fix(codegen): validate custom element tags
All checks were successful
Security Scan / security (push) Successful in 8s
Test / test (push) Successful in 54s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 09:49:42 +00:00
parent c088e5a5ac
commit 8abd428227
4 changed files with 58 additions and 3 deletions

View file

@ -48,6 +48,15 @@ func TestRun_InvalidTag_Bad(t *testing.T) {
assert.Contains(t, err.Error(), "hyphen")
}
func TestRun_InvalidTagCharacters_Bad(t *testing.T) {
input := core.NewReader(`{"H":"Nav-Bar","C":"nav bar"}`)
output := core.NewBuilder()
err := run(input, output, false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "lowercase hyphenated name")
}
func TestRun_EmptySlots_Good(t *testing.T) {
input := core.NewReader(`{}`)
output := core.NewBuilder()

View file

@ -10,6 +10,31 @@ import (
log "dappco.re/go/core/log"
)
// isValidCustomElementTag reports whether tag is a safe custom element name.
// The generator rejects values that would fail at customElements.define() time.
func isValidCustomElementTag(tag string) bool {
if tag == "" || !core.Contains(tag, "-") {
return false
}
if tag[0] < 'a' || tag[0] > 'z' {
return false
}
for i := range len(tag) {
ch := tag[i]
switch {
case ch >= 'a' && ch <= 'z':
case ch >= '0' && ch <= '9':
case ch == '-' || ch == '.' || ch == '_':
default:
return false
}
}
return true
}
// wcTemplate is the Web Component class template.
// Uses closed Shadow DOM for isolation. Content is set via the shadow root's
// DOM API using trusted go-html codegen output (never user input).
@ -35,8 +60,8 @@ var wcTemplate = template.Must(template.New("wc").Parse(`class {{.ClassName}} ex
// GenerateClass produces a JS class definition for a custom element.
// Usage example: js, err := GenerateClass("nav-bar", "H")
func GenerateClass(tag, slot string) (string, error) {
if !core.Contains(tag, "-") {
return "", log.E("codegen.GenerateClass", "custom element tag must contain a hyphen: "+tag, nil)
if !isValidCustomElementTag(tag) {
return "", log.E("codegen.GenerateClass", "custom element tag must be a lowercase hyphenated name: "+tag, nil)
}
b := core.NewBuilder()
err := wcTemplate.Execute(b, struct {

View file

@ -22,6 +22,12 @@ func TestGenerateClass_ValidTag_Good(t *testing.T) {
func TestGenerateClass_InvalidTag_Bad(t *testing.T) {
_, err := GenerateClass("invalid", "C")
assert.Error(t, err, "custom element names must contain a hyphen")
_, err = GenerateClass("Nav-Bar", "C")
assert.Error(t, err, "custom element names must be lowercase")
_, err = GenerateClass("nav bar", "C")
assert.Error(t, err, "custom element names must reject spaces")
}
func TestGenerateRegistration_DefinesCustomElement_Good(t *testing.T) {
@ -99,6 +105,21 @@ func TestGenerateTypeScriptDefinitions_DeduplicatesAndOrders_Good(t *testing.T)
assert.Less(t, strings.Index(dts, `"alpha-panel": AlphaPanel;`), strings.Index(dts, `"zed-panel": ZedPanel;`))
}
func TestGenerateTypeScriptDefinitions_SkipsInvalidTags_Good(t *testing.T) {
slots := map[string]string{
"H": "nav-bar",
"C": "Nav-Bar",
"F": "nav bar",
}
dts := GenerateTypeScriptDefinitions(slots)
assert.Contains(t, dts, `"nav-bar": NavBar;`)
assert.NotContains(t, dts, "Nav-Bar")
assert.NotContains(t, dts, "nav bar")
assert.Equal(t, 1, countSubstr(dts, `export declare class NavBar extends HTMLElement`))
}
func countSubstr(s, substr string) int {
if substr == "" {
return len(s) + 1

View file

@ -28,7 +28,7 @@ func GenerateTypeScriptDefinitions(slots map[string]string) string {
b.WriteString(" interface HTMLElementTagNameMap {\n")
for _, slot := range keys {
tag := slots[slot]
if seen[tag] {
if !isValidCustomElementTag(tag) || seen[tag] {
continue
}
seen[tag] = true