diff --git a/packages/bun-types/html-rewriter.d.ts b/packages/bun-types/html-rewriter.d.ts index 44c9951e4de37d..4db17decb630da 100644 --- a/packages/bun-types/html-rewriter.d.ts +++ b/packages/bun-types/html-rewriter.d.ts @@ -26,6 +26,8 @@ declare namespace HTMLRewriterTypes { readonly name: string | null; readonly publicId: string | null; readonly systemId: string | null; + readonly removed: boolean; + remove(): Doctype; } interface DocumentEnd { diff --git a/src/bun.js/api/html_rewriter.classes.ts b/src/bun.js/api/html_rewriter.classes.ts index a00c20b7e194ba..2e8808402b69f6 100644 --- a/src/bun.js/api/html_rewriter.classes.ts +++ b/src/bun.js/api/html_rewriter.classes.ts @@ -81,6 +81,13 @@ export default [ getter: "publicId", cache: true, }, + remove: { + fn: "remove", + length: 0, + }, + removed: { + getter: "removed", + }, }, }), define({ diff --git a/src/bun.js/api/html_rewriter.zig b/src/bun.js/api/html_rewriter.zig index bab7ac3e5aec19..65ec20108026be 100644 --- a/src/bun.js/api/html_rewriter.zig +++ b/src/bun.js/api/html_rewriter.zig @@ -1157,6 +1157,26 @@ pub const DocType = struct { return JSValue.jsNull(); return ZigString.init(str).toJS(globalObject); } + + pub fn remove( + this: *DocType, + _: *JSGlobalObject, + callFrame: *JSC.CallFrame, + ) bun.JSError!JSValue { + if (this.doctype == null) + return JSValue.jsUndefined(); + this.doctype.?.remove(); + return callFrame.this(); + } + + pub fn removed( + this: *DocType, + _: *JSGlobalObject, + ) JSValue { + if (this.doctype == null) + return JSValue.jsUndefined(); + return JSValue.jsBoolean(this.doctype.?.isRemoved()); + } }; pub const DocEnd = struct { diff --git a/src/deps/lol-html.zig b/src/deps/lol-html.zig index bafd833c90547a..303588edc09276 100644 --- a/src/deps/lol-html.zig +++ b/src/deps/lol-html.zig @@ -796,6 +796,8 @@ pub const DocType = opaque { extern fn lol_html_doctype_system_id_get(doctype: *const DocType) HTMLString; extern fn lol_html_doctype_user_data_set(doctype: *const DocType, user_data: ?*anyopaque) void; extern fn lol_html_doctype_user_data_get(doctype: *const DocType) ?*anyopaque; + extern fn lol_html_doctype_remove(doctype: *DocType) void; + extern fn lol_html_doctype_is_removed(doctype: *const DocType) bool; pub const Callback = *const fn (*DocType, ?*anyopaque) callconv(.C) Directive; @@ -811,6 +813,14 @@ pub const DocType = opaque { auto_disable(); return this.lol_html_doctype_system_id_get(); } + pub fn remove(this: *DocType) void { + auto_disable(); + return this.lol_html_doctype_remove(); + } + pub fn isRemoved(this: *const DocType) bool { + auto_disable(); + return this.lol_html_doctype_is_removed(); + } }; pub const Encoding = enum { diff --git a/test/js/web/html/html-rewriter-doctype.test.ts b/test/js/web/html/html-rewriter-doctype.test.ts new file mode 100644 index 00000000000000..632a6fccb31071 --- /dev/null +++ b/test/js/web/html/html-rewriter-doctype.test.ts @@ -0,0 +1,24 @@ +import { expect, test, describe } from "bun:test"; + +describe("HTMLRewriter DOCTYPE handler", () => { + test("remove and removed property work on DOCTYPE", () => { + const html = "Hello"; + let sawDoctype = false; + let wasRemoved = false; + + const rewriter = new HTMLRewriter().onDocument({ + doctype(doctype) { + sawDoctype = true; + doctype.remove(); + wasRemoved = doctype.removed; + }, + }); + + const result = rewriter.transform(html); + + expect(sawDoctype).toBe(true); + expect(wasRemoved).toBe(true); + expect(result).not.toContain(""); + }); +});