1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
package syntax
import (
"strings"
"testing"
)
// xmlClassOf returns the class covering the first occurrence of a piece of text
// in an XML document.
func xmlClassOf(t *testing.T, src, want string) Class {
t.Helper()
index := strings.Index(src, want)
if index < 0 {
t.Fatalf("%q does not appear in the source", want)
}
line := strings.Count(src[:index], "\n")
col := index - (strings.LastIndex(src[:index], "\n") + 1)
class, ok := classAt(Highlight(LanguageXML, src), line, col)
if !ok {
t.Fatalf("no span covers %q at line %d column %d", want, line, col)
}
return class
}
func TestXMLColoursItsParts(t *testing.T) {
const src = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note SYSTEM "note.dtd">
<!-- a comment -->
<xsl:template match="/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<to>Tove</to>
&
</xsl:template>
`
tests := map[string]Class{
"<?xml": ClassKeyword,
"version": ClassAttribute,
`"1.0"`: ClassString,
"<!DOCTYPE": ClassKeyword,
"<!-- a comment -->": ClassComment,
"<xsl:template": ClassTag,
"match": ClassAttribute,
"&": ClassConstant,
}
for text, want := range tests {
t.Run(text, func(t *testing.T) {
if got := xmlClassOf(t, src, text); got != want {
t.Errorf("%q is %v, want %v", text, got, want)
}
})
}
}
func TestANamespacedNameIsOneSpan(t *testing.T) {
// Splitting the prefix from the local name would be two colours for one
// name, in the documents where namespaces are used most.
const name = "<xsl:template"
src := name + ">\n</xsl:template>\n"
spans := Highlight(LanguageXML, src)
for col := 0; col < len(name); col++ {
if class, ok := classAt(spans, 0, col); !ok || class != ClassTag {
t.Fatalf("column %d of %q is %v (covered: %v)", col, name, class, ok)
}
}
}
func TestTheEqualsBetweenAnAttributeAndItsValueIsAnOperator(t *testing.T) {
const src = `<a href="x"/>`
if got := xmlClassOf(t, src, "="); got != ClassOperator {
t.Errorf("the = is %v, want operator", got)
}
}
func TestASelfClosingTagIsClosed(t *testing.T) {
const src = `<br/>`
spans := Highlight(LanguageXML, src)
if class, ok := classAt(spans, 0, 3); !ok || class != ClassTag {
t.Errorf("the /> is %v (covered: %v), want tag", class, ok)
}
}
func TestCDATAContentsAreNotColouredAsMarkup(t *testing.T) {
// The reason XML has a scanner of its own. Everything between the
// delimiters is text, and a `<not>` inside one is not a tag.
const src = `<a><![CDATA[ <not> & markup ]]></a>`
spans := Highlight(LanguageXML, src)
start := strings.Index(src, "<![CDATA[")
end := strings.Index(src, "]]>") + len("]]>")
for col := start; col < end; col++ {
class, ok := classAt(spans, 0, col)
if !ok || class != ClassString {
t.Fatalf("column %d of the CDATA section is %v (covered: %v), want string", col-start, class, ok)
}
}
// And the document carries on as markup afterwards.
if class, ok := classAt(spans, 0, end); !ok || class != ClassTag {
t.Errorf("the tag after the CDATA is %v (covered: %v), want tag", class, ok)
}
}
func TestCDATACarriesAcrossLines(t *testing.T) {
const src = "<a><![CDATA[\n <not> markup\n]]></a>\n"
spans := Highlight(LanguageXML, src)
if class, ok := classAt(spans, 1, 2); !ok || class != ClassString {
t.Errorf("the second line of the CDATA is %v (covered: %v), want string", class, ok)
}
if class, ok := classAt(spans, 2, 3); !ok || class != ClassTag {
t.Errorf("the tag after the CDATA is %v (covered: %v), want tag", class, ok)
}
}
func TestACommentCarriesAcrossLines(t *testing.T) {
const src = "<!-- one\ntwo\n--><a/>\n"
spans := Highlight(LanguageXML, src)
if class, ok := classAt(spans, 1, 0); !ok || class != ClassComment {
t.Errorf("the second line of the comment is %v (covered: %v), want comment", class, ok)
}
if class, ok := classAt(spans, 2, 3); !ok || class != ClassTag {
t.Errorf("the tag after the comment is %v (covered: %v), want tag", class, ok)
}
}
func TestACommentAndACDATAAreClosedByDifferentDelimiters(t *testing.T) {
// A single carried flag would close whichever came first on either, so a
// `-->` inside a CDATA section would end it.
const src = "<a><![CDATA[\n a --> b\n]]></a>\n"
spans := Highlight(LanguageXML, src)
if class, ok := classAt(spans, 1, 2); !ok || class != ClassString {
t.Errorf("a --> inside CDATA ended it: line 1 is %v (covered: %v)", class, ok)
}
}
func TestABareAmpersandIsLeftAloneInXML(t *testing.T) {
// It is legal text in plenty of documents, and swallowing the rest of the
// line looking for a semicolon would be worse than missing an entity.
const src = `<a>Tom & Jerry</a>`
spans := Highlight(LanguageXML, src)
if class, ok := classAt(spans, 0, strings.Index(src, "&")); ok && class == ClassConstant {
t.Error("a bare & was coloured as an entity")
}
if class, ok := classAt(spans, 0, strings.Index(src, "</a>")); !ok || class != ClassTag {
t.Errorf("the closing tag is %v (covered: %v); the & swallowed it", class, ok)
}
}
func TestANumericEntityIsAConstant(t *testing.T) {
if got := xmlClassOf(t, "<a>©</a>", "©"); got != ClassConstant {
t.Errorf("a numeric entity is %v, want constant", got)
}
}
func TestXMLIsRecognisedByItsManyExtensions(t *testing.T) {
for _, name := range []string{
"pom.xml", "icon.svg", "schema.xsd", "sheet.xsl", "sheet.xslt",
"Info.plist", "app.csproj",
} {
t.Run(name, func(t *testing.T) {
if got := LanguageOf(name, ""); got != LanguageXML {
t.Errorf("LanguageOf(%q) = %q, want xml", name, got)
}
})
}
}
func TestHTMLIsStillHTML(t *testing.T) {
// Adding XML must not take the .html extension with it.
for _, name := range []string{"index.html", "page.htm"} {
if got := LanguageOf(name, ""); got != LanguageHTML {
t.Errorf("LanguageOf(%q) = %q, want html", name, got)
}
}
}
func TestXMLReturnsOneEntryPerLine(t *testing.T) {
tests := map[string]int{
"": 1,
"<a/>": 1,
"<a/>\n": 2,
"<a>\n <b/>\n</a>\n": 4,
}
for src, want := range tests {
if got := len(Highlight(LanguageXML, src)); got != want {
t.Errorf("Highlight(%q) returned %d lines, want %d", src, got, want)
}
}
}
func TestBrokenXMLIsStillColoured(t *testing.T) {
for _, src := range []string{"<a", "<!--", "<![CDATA[", "<?xml", "&", "</"} {
t.Run(src, func(t *testing.T) {
if got := len(Highlight(LanguageXML, src)); got != 1 {
t.Errorf("Highlight(%q) returned %d lines, want 1", src, got)
}
})
}
}
func TestXMLSpansAreOrderedAndDoNotOverlap(t *testing.T) {
const src = "<?xml version=\"1.0\"?>\n<!-- c -->\n<ns:a b=\"1\">&<![CDATA[x]]></ns:a>\n"
for line, onLine := range Highlight(LanguageXML, src) {
previousEnd := 0
for _, span := range onLine {
if span.Start < previousEnd {
t.Errorf("line %d: span %+v starts before the previous one ended at %d", line, span, previousEnd)
}
previousEnd = span.End
}
}
}
|