fix(codegen): validate custom element tags
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
c088e5a5ac
commit
8abd428227
4 changed files with 58 additions and 3 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue