Skip to content

Commit d0ea222

Browse files
committed
fix: added node v18 polyfills and set engines
1 parent 8e956fd commit d0ea222

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
"@types/node": "^22.0.2",
1717
"typescript": "^5.4.3"
1818
},
19+
"engines": {
20+
"node": ">= 18"
21+
},
1922
"homepage": "https://github.com/cleandns-inc/tool-whois#readme",
2023
"license": "ISC",
2124
"main": "dist/index.js",

src/index.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { describe, it } from "node:test";
22
import assert from "node:assert";
3+
4+
// Import polyfills first
5+
import "./polyfills.js";
6+
37
import { toArray, escapeRegex, validateDomain } from "./index.js";
48

59
// ============================================================================
@@ -358,3 +362,77 @@ describe("IP response parsing", () => {
358362
assert.strictEqual(response.registrar.name, "ARIN");
359363
});
360364
});
365+
366+
367+
// ============================================================================
368+
// String.prototype.toWellFormed polyfill tests (Node.js 18 compatibility)
369+
// ============================================================================
370+
371+
describe("String.prototype.toWellFormed polyfill", () => {
372+
it("toWellFormed is available as a function", () => {
373+
assert.strictEqual(typeof "".toWellFormed, "function");
374+
});
375+
376+
it("returns same string for well-formed input", () => {
377+
const str = "Hello, World!";
378+
assert.strictEqual(str.toWellFormed(), str);
379+
});
380+
381+
it("handles empty string", () => {
382+
assert.strictEqual("".toWellFormed(), "");
383+
});
384+
385+
it("handles valid surrogate pairs (emoji)", () => {
386+
const emoji = "😀"; // U+1F600 = \uD83D\uDE00
387+
assert.strictEqual(emoji.toWellFormed(), emoji);
388+
});
389+
390+
it("replaces lone high surrogate with U+FFFD", () => {
391+
const loneHigh = "abc\uD800def";
392+
assert.strictEqual(loneHigh.toWellFormed(), "abc\uFFFDdef");
393+
});
394+
395+
it("replaces lone low surrogate with U+FFFD", () => {
396+
const loneLow = "abc\uDC00def";
397+
assert.strictEqual(loneLow.toWellFormed(), "abc\uFFFDdef");
398+
});
399+
400+
it("replaces lone high surrogate at end", () => {
401+
const str = "test\uD800";
402+
assert.strictEqual(str.toWellFormed(), "test\uFFFD");
403+
});
404+
405+
it("handles multiple lone surrogates", () => {
406+
const str = "\uD800\uD800";
407+
assert.strictEqual(str.toWellFormed(), "\uFFFD\uFFFD");
408+
});
409+
410+
it("preserves valid surrogate pairs among lone surrogates", () => {
411+
const str = "\uD800\uD83D\uDE00\uDC00"; // lone high, valid pair, lone low
412+
assert.strictEqual(str.toWellFormed(), "\uFFFD😀\uFFFD");
413+
});
414+
});
415+
416+
describe("String.prototype.isWellFormed polyfill", () => {
417+
it("isWellFormed is available as a function", () => {
418+
assert.strictEqual(typeof "".isWellFormed, "function");
419+
});
420+
421+
it("returns true for well-formed strings", () => {
422+
assert.strictEqual("Hello".isWellFormed(), true);
423+
assert.strictEqual("".isWellFormed(), true);
424+
assert.strictEqual("😀".isWellFormed(), true);
425+
});
426+
427+
it("returns false for lone high surrogate", () => {
428+
assert.strictEqual("test\uD800".isWellFormed(), false);
429+
});
430+
431+
it("returns false for lone low surrogate", () => {
432+
assert.strictEqual("test\uDC00".isWellFormed(), false);
433+
});
434+
435+
it("returns true for valid surrogate pair", () => {
436+
assert.strictEqual("\uD83D\uDE00".isWellFormed(), true);
437+
});
438+
});

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Polyfills for Node.js 18 compatibility
2+
import "./polyfills.js";
3+
14
import { WhoisOptions, WhoisResponse, WhoisTimestampFields } from "../whois.js";
25
import { parseIpResponse } from "./ip.js";
36
import { determinePort43Domain, port43 } from "./port43.js";

src/polyfills.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Polyfills for Node.js 18 compatibility.
3+
* String.prototype.toWellFormed was added in Node.js 20.
4+
*/
5+
6+
// Extend String interface for TypeScript
7+
declare global {
8+
interface String {
9+
toWellFormed(): string;
10+
isWellFormed(): boolean;
11+
}
12+
}
13+
14+
// Polyfill for String.prototype.toWellFormed (ES2024)
15+
// Replaces lone surrogates with U+FFFD (replacement character)
16+
if (typeof String.prototype.toWellFormed !== 'function') {
17+
String.prototype.toWellFormed = function () {
18+
const str = String(this);
19+
const len = str.length;
20+
let result = '';
21+
22+
for (let i = 0; i < len; i++) {
23+
const code = str.charCodeAt(i);
24+
25+
// Check for lone surrogates
26+
if (code >= 0xD800 && code <= 0xDBFF) {
27+
// High surrogate
28+
if (i + 1 < len) {
29+
const next = str.charCodeAt(i + 1);
30+
if (next >= 0xDC00 && next <= 0xDFFF) {
31+
// Valid surrogate pair
32+
result += str[i] + str[i + 1];
33+
i++;
34+
continue;
35+
}
36+
}
37+
// Lone high surrogate - replace with U+FFFD
38+
result += '\uFFFD';
39+
} else if (code >= 0xDC00 && code <= 0xDFFF) {
40+
// Lone low surrogate - replace with U+FFFD
41+
result += '\uFFFD';
42+
} else {
43+
result += str[i];
44+
}
45+
}
46+
47+
return result;
48+
};
49+
}
50+
51+
// Polyfill for String.prototype.isWellFormed (ES2024)
52+
if (typeof String.prototype.isWellFormed !== 'function') {
53+
String.prototype.isWellFormed = function () {
54+
const str = String(this);
55+
const len = str.length;
56+
57+
for (let i = 0; i < len; i++) {
58+
const code = str.charCodeAt(i);
59+
60+
if (code >= 0xD800 && code <= 0xDBFF) {
61+
// High surrogate - check for valid pair
62+
if (i + 1 >= len) return false;
63+
const next = str.charCodeAt(i + 1);
64+
if (next < 0xDC00 || next > 0xDFFF) return false;
65+
i++; // Skip the low surrogate
66+
} else if (code >= 0xDC00 && code <= 0xDFFF) {
67+
// Lone low surrogate
68+
return false;
69+
}
70+
}
71+
72+
return true;
73+
};
74+
}
75+
76+
export {};

0 commit comments

Comments
 (0)