feat(codegen): reject reserved custom element tags
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-04 01:31:38 +00:00
parent 5fc370b691
commit 799317b788
4 changed files with 65 additions and 1 deletions

View file

@ -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]

View file

@ -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 {

View file

@ -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 {

View file

@ -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"))
}