100 lines
3.2 KiB
Go
100 lines
3.2 KiB
Go
package lexer
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The highlighter emits HTML, and its input is Go source — which is full of <, > and &.
|
|
// Anything that reaches the page unescaped is markup injection into your own docs page:
|
|
// a snippet containing `<div>` would render a div.
|
|
func TestHighlightGoEscapes(t *testing.T) {
|
|
got := HighlightGo(`s := "<div class=\"x\">" // a & b`)
|
|
|
|
if strings.Contains(got, "<div") {
|
|
t.Errorf("a `<div>` in the SOURCE reached the output as markup:\n%s", got)
|
|
}
|
|
if !strings.Contains(got, "<div") {
|
|
t.Errorf("the angle bracket was not escaped:\n%s", got)
|
|
}
|
|
if !strings.Contains(got, "&") {
|
|
t.Errorf("the ampersand was not escaped:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestHighlightGoClassifies(t *testing.T) {
|
|
got := HighlightGo("func main() { x := 42 // note\n}")
|
|
|
|
for _, want := range []struct{ what, class, text string }{
|
|
{"keyword", KeywordClass, "func"},
|
|
{"call", FuncClass, "main"},
|
|
{"number", NumberClass, "42"},
|
|
{"comment", CommentClass, "// note"},
|
|
} {
|
|
if !strings.Contains(got, `<span class="`+want.class+`">`+want.text+`</span>`) {
|
|
t.Errorf("%s %q was not highlighted:\n%s", want.what, want.text, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The //gowasm: directives are the most important line in half these snippets. They are
|
|
// comments, and must survive as such.
|
|
func TestHighlightGoKeepsDirectives(t *testing.T) {
|
|
got := HighlightGo("//gowasm:page / static layout=public\nfunc HomePage() {}")
|
|
if !strings.Contains(got, `<span class="`+CommentClass+`">//gowasm:page / static layout=public</span>`) {
|
|
t.Errorf("the directive was not kept whole as a comment:\n%s", got)
|
|
}
|
|
}
|
|
|
|
// A lexer that can hang or eat the rest of the file on malformed input would take the
|
|
// whole page down with it. Unterminated literals stop; they do not run away.
|
|
func TestHighlightGoSurvivesMalformedInput(t *testing.T) {
|
|
for _, src := range []string{
|
|
`x := "unterminated`,
|
|
"y := `unterminated raw",
|
|
"/* unterminated block",
|
|
`z := '`,
|
|
"",
|
|
} {
|
|
got := HighlightGo(src)
|
|
// The text must all still be there — mangling is not an acceptable failure mode
|
|
// either. Compare on the visible characters, ignoring the spans.
|
|
if plain := stripTags(got); plain != src {
|
|
t.Errorf("input %q came out as %q", src, plain)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nothing is dropped: every byte of the source is still on the page, in order.
|
|
func TestHighlightGoIsLossless(t *testing.T) {
|
|
src := "package app\n\nimport \"strings\"\n\nfunc f(n int) string {\n\treturn strings.Repeat(\"x\", n) // pad\n}\n"
|
|
if plain := stripTags(HighlightGo(src)); plain != src {
|
|
t.Errorf("the highlighter changed the source.\n got: %q\nwant: %q", plain, src)
|
|
}
|
|
}
|
|
|
|
// stripTags removes the spans and unescapes, recovering the original source.
|
|
func stripTags(s string) string {
|
|
var b strings.Builder
|
|
for i := 0; i < len(s); {
|
|
if s[i] == '<' {
|
|
j := strings.IndexByte(s[i:], '>')
|
|
if j < 0 {
|
|
break
|
|
}
|
|
i += j + 1
|
|
continue
|
|
}
|
|
b.WriteByte(s[i])
|
|
i++
|
|
}
|
|
out := b.String()
|
|
// Reverse html.EscapeString, innermost last.
|
|
out = strings.ReplaceAll(out, "<", "<")
|
|
out = strings.ReplaceAll(out, ">", ">")
|
|
out = strings.ReplaceAll(out, """, `"`)
|
|
out = strings.ReplaceAll(out, "'", "'")
|
|
out = strings.ReplaceAll(out, "&", "&")
|
|
return out
|
|
}
|