From 799317b788ecac9368bb8e53bd29f10ea6e16c1b Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 01:31:38 +0000 Subject: [PATCH] feat(codegen): reject reserved custom element tags Co-Authored-By: Virgil --- cmd/wasm/components.go | 13 +++++++++++++ cmd/wasm/register_test.go | 1 + codegen/codegen.go | 25 ++++++++++++++++++++++++- codegen/codegen_test.go | 27 +++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/cmd/wasm/components.go b/cmd/wasm/components.go index f9f5673..c447ab2 100644 --- a/cmd/wasm/components.go +++ b/cmd/wasm/components.go @@ -7,6 +7,16 @@ import ( ) var canonicalSlotOrder = []string{"H", "L", "C", "R", "F"} +var reservedCustomElementTags = map[string]struct{}{ + "annotation-xml": {}, + "color-profile": {}, + "font-face": {}, + "font-face-src": {}, + "font-face-uri": {}, + "font-face-format": {}, + "font-face-name": {}, + "missing-glyph": {}, +} // cmd/wasm/components.go: isValidCustomElementTag reports whether tag is a safe // custom element name. @@ -19,6 +29,9 @@ func isValidCustomElementTag(tag string) bool { if tag[0] < 'a' || tag[0] > 'z' { return false } + if _, reserved := reservedCustomElementTags[tag]; reserved { + return false + } for i := range len(tag) { ch := tag[i] diff --git a/cmd/wasm/register_test.go b/cmd/wasm/register_test.go index 313aa54..8cdcccb 100644 --- a/cmd/wasm/register_test.go +++ b/cmd/wasm/register_test.go @@ -50,6 +50,7 @@ func TestIsValidCustomElementTag(t *testing.T) { {tag: "NavBar", want: false}, {tag: "nav", want: false}, {tag: "nav_bar", want: false}, + {tag: "annotation-xml", want: false}, } for _, tt := range tests { diff --git a/codegen/codegen.go b/codegen/codegen.go index 229276b..85e8a6e 100644 --- a/codegen/codegen.go +++ b/codegen/codegen.go @@ -11,6 +11,22 @@ import ( // isValidCustomElementTag reports whether tag is a safe custom element name. // The generator rejects values that would fail at customElements.define() time. +var reservedCustomElementTags = map[string]struct{}{ + "annotation-xml": {}, + "color-profile": {}, + "font-face": {}, + "font-face-src": {}, + "font-face-uri": {}, + "font-face-format": {}, + "font-face-name": {}, + "missing-glyph": {}, +} + +func isReservedCustomElementTag(tag string) bool { + _, reserved := reservedCustomElementTags[tag] + return reserved +} + func isValidCustomElementTag(tag string) bool { if tag == "" || !strings.Contains(tag, "-") { return false @@ -18,6 +34,9 @@ func isValidCustomElementTag(tag string) bool { if tag[0] < 'a' || tag[0] > 'z' { return false } + if isReservedCustomElementTag(tag) { + return false + } for i := range len(tag) { ch := tag[i] @@ -60,7 +79,11 @@ var wcTemplate = template.Must(template.New("wc").Parse(`class {{.ClassName}} ex // Example: cls, err := GenerateClass("nav-bar", "H") func GenerateClass(tag, slot string) (string, error) { if !isValidCustomElementTag(tag) { - return "", log.E("codegen.GenerateClass", "custom element tag must be a lowercase hyphenated name: "+tag, nil) + message := "custom element tag must be a lowercase hyphenated name: " + tag + if isReservedCustomElementTag(tag) { + message = "custom element tag is reserved by the Web Components spec: " + tag + } + return "", log.E("codegen.GenerateClass", message, nil) } var b strings.Builder err := wcTemplate.Execute(&b, struct { diff --git a/codegen/codegen_test.go b/codegen/codegen_test.go index 36df710..0eac9ed 100644 --- a/codegen/codegen_test.go +++ b/codegen/codegen_test.go @@ -28,6 +28,9 @@ func TestGenerateClass_Bad_InvalidTag(t *testing.T) { _, err = GenerateClass("nav bar", "C") assert.Error(t, err, "custom element names must reject spaces") + + _, err = GenerateClass("annotation-xml", "C") + assert.Error(t, err, "reserved custom element names must be rejected") } func TestGenerateRegistration_Good(t *testing.T) { @@ -96,6 +99,16 @@ func TestGenerateBundle_Bad_InvalidSlotKey(t *testing.T) { assert.Contains(t, err.Error(), `"X"`) } +func TestGenerateBundle_Bad_ReservedTag(t *testing.T) { + slots := map[string]string{ + "H": "annotation-xml", + } + + _, err := GenerateBundle(slots) + require.Error(t, err) + assert.Contains(t, err.Error(), "reserved") +} + func TestGenerateTypeDefinitions_Good(t *testing.T) { slots := map[string]string{ "H": "nav-bar", @@ -145,3 +158,17 @@ func TestGenerateTypeDefinitions_SkipsInvalidTags(t *testing.T) { assert.NotContains(t, dts, "nav bar") assert.Equal(t, 1, strings.Count(dts, "extends HTMLElement")) } + +func TestGenerateTypeDefinitions_SkipsReservedTags(t *testing.T) { + slots := map[string]string{ + "H": "annotation-xml", + "C": "nav-bar", + } + + dts, err := GenerateTypeDefinitions(slots) + require.NoError(t, err) + + assert.Contains(t, dts, `"nav-bar": NavBar;`) + assert.NotContains(t, dts, "annotation-xml") + assert.Equal(t, 1, strings.Count(dts, "extends HTMLElement")) +}