Skip to content

Commit d9dd73f

Browse files
Add templ XML lexer (#1283)
Adds a templ lexer as an XML lexer definition under `lexers/embedded/templ.xml`. This replaces the earlier Go-based lexer approach from #1277, following maintainer feedback to use an XML lexer instead. Includes fixtures for: - templ component declarations - HTML tags and attributes - Go expressions in `{ ... }` - braced attribute values - templ control flow (`if` / `else`) - component calls with `@Button(...)` - analyser coverage Local test gates: - `go test ./lexers -count=1` - `go test ./... -count=1`
1 parent effffdb commit d9dd73f

5 files changed

Lines changed: 721 additions & 0 deletions

File tree

lexers/embedded/templ.xml

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
<lexer>
2+
<config>
3+
<name>Templ</name>
4+
<alias>templ</alias>
5+
<filename>*.templ</filename>
6+
<mime_type>text/x-templ</mime_type>
7+
<dot_all>true</dot_all>
8+
<analyse>
9+
<regex pattern="(?m)^\s*templ\s+[A-Za-z_]\w*\s*\(" score="0.7"/>
10+
<regex pattern="(?m)^\s*package\s+\w+[\s\S]*^\s*templ\s+" score="0.5"/>
11+
</analyse>
12+
</config>
13+
<rules>
14+
<state name="root">
15+
<rule pattern="//[^\n\r]*">
16+
<token type="CommentSingle"/>
17+
</rule>
18+
<rule pattern="/\*(?:.|\n)*?\*/">
19+
<token type="CommentMultiline"/>
20+
</rule>
21+
<rule pattern="(?m)^(\s*)(package|import|const|type|func)([^\n]*)">
22+
<bygroups>
23+
<token type="TextWhitespace"/>
24+
<using lexer="Go"/>
25+
<using lexer="Go"/>
26+
</bygroups>
27+
</rule>
28+
<rule pattern="\b(templ|css|script)(\s+)([A-Za-z_]\w*)(\s*)(\([^{}]*\))(\s*)({)">
29+
<bygroups>
30+
<token type="KeywordDeclaration"/>
31+
<token type="TextWhitespace"/>
32+
<token type="NameFunction"/>
33+
<token type="TextWhitespace"/>
34+
<using lexer="Go"/>
35+
<token type="TextWhitespace"/>
36+
<token type="Punctuation"/>
37+
</bygroups>
38+
</rule>
39+
<rule pattern="@[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?:\([^{}()\r\n]*(?:\([^{}()\r\n]*\)[^{}()\r\n]*)*\))?(?:[ \t]*{)?">
40+
<token type="NameFunction"/>
41+
</rule>
42+
<rule pattern="(?m)^(\s*)(if|for|switch|select)(\s+)([^{}\n]*)(\s*)({)">
43+
<bygroups>
44+
<token type="TextWhitespace"/>
45+
<token type="Keyword"/>
46+
<token type="TextWhitespace"/>
47+
<using lexer="Go"/>
48+
<token type="TextWhitespace"/>
49+
<token type="Punctuation"/>
50+
</bygroups>
51+
</rule>
52+
<rule pattern="(?m)^(\s*)(else)(\s*)(if)?(\s*)([^{}\n]*)(\s*)({)?">
53+
<bygroups>
54+
<token type="TextWhitespace"/>
55+
<token type="Keyword"/>
56+
<token type="TextWhitespace"/>
57+
<token type="Keyword"/>
58+
<token type="TextWhitespace"/>
59+
<using lexer="Go"/>
60+
<token type="TextWhitespace"/>
61+
<token type="Punctuation"/>
62+
</bygroups>
63+
</rule>
64+
<rule pattern="(})(\s*)(else)(\s*)(if)?(\s*)([^{}\n]*)(\s*)({)?">
65+
<bygroups>
66+
<token type="Punctuation"/>
67+
<token type="TextWhitespace"/>
68+
<token type="Keyword"/>
69+
<token type="TextWhitespace"/>
70+
<token type="Keyword"/>
71+
<token type="TextWhitespace"/>
72+
<using lexer="Go"/>
73+
<token type="TextWhitespace"/>
74+
<token type="Punctuation"/>
75+
</bygroups>
76+
</rule>
77+
<rule pattern="({)([^{}\n]*)(})">
78+
<bygroups>
79+
<token type="Punctuation"/>
80+
<using lexer="Go"/>
81+
<token type="Punctuation"/>
82+
</bygroups>
83+
</rule>
84+
<rule pattern="}">
85+
<token type="Punctuation"/>
86+
</rule>
87+
<rule pattern="`(?:.|\n)*?`">
88+
<token type="LiteralStringBacktick"/>
89+
</rule>
90+
<rule pattern="\s+">
91+
<token type="TextWhitespace"/>
92+
</rule>
93+
<rule pattern="[^&lt;&amp;@{}`\s]+">
94+
<token type="Text"/>
95+
</rule>
96+
<rule pattern="&amp;\S*?;">
97+
<token type="NameEntity"/>
98+
</rule>
99+
<rule pattern="\&lt;\!\[CDATA\[.*?\]\]\&gt;">
100+
<token type="CommentPreproc"/>
101+
</rule>
102+
<rule pattern="&lt;!--">
103+
<token type="Comment"/>
104+
<push state="comment"/>
105+
</rule>
106+
<rule pattern="&lt;\?.*?\?&gt;">
107+
<token type="CommentPreproc"/>
108+
</rule>
109+
<rule pattern="&lt;![^&gt;]*&gt;">
110+
<token type="CommentPreproc"/>
111+
</rule>
112+
<rule pattern="(&lt;)(script)(\s*)">
113+
<bygroups>
114+
<token type="Punctuation"/>
115+
<token type="NameTag"/>
116+
<token type="Text"/>
117+
</bygroups>
118+
<push state="script-content" state="tag"/>
119+
</rule>
120+
<rule pattern="(&lt;)(style)(\s*)">
121+
<bygroups>
122+
<token type="Punctuation"/>
123+
<token type="NameTag"/>
124+
<token type="Text"/>
125+
</bygroups>
126+
<push state="style-content" state="tag"/>
127+
</rule>
128+
<rule pattern="(&lt;)([A-Za-z][\w:.-]*)">
129+
<bygroups>
130+
<token type="Punctuation"/>
131+
<token type="NameTag"/>
132+
</bygroups>
133+
<push state="tag"/>
134+
</rule>
135+
<rule pattern="(&lt;/)([A-Za-z][\w:.-]*)(\s*)(&gt;)">
136+
<bygroups>
137+
<token type="Punctuation"/>
138+
<token type="NameTag"/>
139+
<token type="Text"/>
140+
<token type="Punctuation"/>
141+
</bygroups>
142+
</rule>
143+
<rule pattern="[@{}&lt;&amp;]">
144+
<token type="Punctuation"/>
145+
</rule>
146+
</state>
147+
<state name="script-content">
148+
<rule pattern="(&lt;)(\s*)(/)(\s*)(script)(\s*)(&gt;)">
149+
<bygroups>
150+
<token type="Punctuation"/>
151+
<token type="Text"/>
152+
<token type="Punctuation"/>
153+
<token type="Text"/>
154+
<token type="NameTag"/>
155+
<token type="Text"/>
156+
<token type="Punctuation"/>
157+
</bygroups>
158+
<pop depth="1"/>
159+
</rule>
160+
<rule pattern="\s*[^\r\n]*\{\{.*?\}\}[^\r\n]*">
161+
<token type="Other"/>
162+
</rule>
163+
<rule pattern=".+?(?=&lt;\s*/\s*script\s*&gt;)">
164+
<token type="Other"/>
165+
</rule>
166+
</state>
167+
<state name="style-content">
168+
<rule pattern="(&lt;)(\s*)(/)(\s*)(style)(\s*)(&gt;)">
169+
<bygroups>
170+
<token type="Punctuation"/>
171+
<token type="Text"/>
172+
<token type="Punctuation"/>
173+
<token type="Text"/>
174+
<token type="NameTag"/>
175+
<token type="Text"/>
176+
<token type="Punctuation"/>
177+
</bygroups>
178+
<pop depth="1"/>
179+
</rule>
180+
<rule pattern=".+?(?=&lt;\s*/\s*style\s*&gt;)">
181+
<using lexer="CSS"/>
182+
</rule>
183+
</state>
184+
<state name="comment">
185+
<rule pattern="[^-]+">
186+
<token type="Comment"/>
187+
</rule>
188+
<rule pattern="--&gt;">
189+
<token type="Comment"/>
190+
<pop depth="1"/>
191+
</rule>
192+
<rule pattern="-">
193+
<token type="Comment"/>
194+
</rule>
195+
</state>
196+
<state name="tag">
197+
<rule pattern="\s+">
198+
<token type="Text"/>
199+
</rule>
200+
<rule pattern="//[^\n\r]*">
201+
<token type="CommentSingle"/>
202+
</rule>
203+
<rule pattern="/\*(?:.|\n)*?\*/">
204+
<token type="CommentMultiline"/>
205+
</rule>
206+
<rule pattern="\b(if|for|switch|select)(\s+)([^{}]*)(\s*)({)">
207+
<bygroups>
208+
<token type="Keyword"/>
209+
<token type="Text"/>
210+
<using lexer="Go"/>
211+
<token type="Text"/>
212+
<token type="Punctuation"/>
213+
</bygroups>
214+
</rule>
215+
<rule pattern="\b(else)(\s*)(if)?(\s*)([^{}]*)(\s*)({)?">
216+
<bygroups>
217+
<token type="Keyword"/>
218+
<token type="Text"/>
219+
<token type="Keyword"/>
220+
<token type="Text"/>
221+
<using lexer="Go"/>
222+
<token type="Text"/>
223+
<token type="Punctuation"/>
224+
</bygroups>
225+
</rule>
226+
<rule pattern="}">
227+
<token type="Punctuation"/>
228+
</rule>
229+
<rule pattern="({)([^{}]*)(})(\??)(\s*)(=)(\s*)({)([^{}]*)(})">
230+
<bygroups>
231+
<token type="Punctuation"/>
232+
<using lexer="Go"/>
233+
<token type="Punctuation"/>
234+
<token type="Operator"/>
235+
<token type="Text"/>
236+
<token type="Operator"/>
237+
<token type="Text"/>
238+
<token type="Punctuation"/>
239+
<using lexer="Go"/>
240+
<token type="Punctuation"/>
241+
</bygroups>
242+
</rule>
243+
<rule pattern="({)([^{}]*)(})(\??)(\s*)(=)(\s*)">
244+
<bygroups>
245+
<token type="Punctuation"/>
246+
<using lexer="Go"/>
247+
<token type="Punctuation"/>
248+
<token type="Operator"/>
249+
<token type="Text"/>
250+
<token type="Operator"/>
251+
<token type="Text"/>
252+
</bygroups>
253+
<push state="attr"/>
254+
</rule>
255+
<rule pattern="({)([^{}]*)(})(\??)">
256+
<bygroups>
257+
<token type="Punctuation"/>
258+
<using lexer="Go"/>
259+
<token type="Punctuation"/>
260+
<token type="Operator"/>
261+
</bygroups>
262+
</rule>
263+
<rule pattern="({)([^{}]*)(})">
264+
<bygroups>
265+
<token type="Punctuation"/>
266+
<using lexer="Go"/>
267+
<token type="Punctuation"/>
268+
</bygroups>
269+
</rule>
270+
<rule pattern="((?:[@#.][\w:.*-]+|\[[^\]\s=]+\]|[\w:.*-]+\??)\s*)(=)(\s*)({)([^{}]*)(})">
271+
<bygroups>
272+
<token type="NameAttribute"/>
273+
<token type="Operator"/>
274+
<token type="Text"/>
275+
<token type="Punctuation"/>
276+
<using lexer="Go"/>
277+
<token type="Punctuation"/>
278+
</bygroups>
279+
</rule>
280+
<rule pattern="((?:[@#.][\w:.*-]+|\[[^\]\s=]+\]|[\w:.*-]+\??)\s*)(=)(\s*)">
281+
<bygroups>
282+
<token type="NameAttribute"/>
283+
<token type="Operator"/>
284+
<token type="Text"/>
285+
</bygroups>
286+
<push state="attr"/>
287+
</rule>
288+
<rule pattern="(?:[@#.][\w:.*-]+|\[[^\]\s=]+\]|[\w:.*-]+\??)">
289+
<token type="NameAttribute"/>
290+
</rule>
291+
<rule pattern="(/?)(\s*)(&gt;)">
292+
<bygroups>
293+
<token type="Punctuation"/>
294+
<token type="Text"/>
295+
<token type="Punctuation"/>
296+
</bygroups>
297+
<pop depth="1"/>
298+
</rule>
299+
</state>
300+
<state name="attr">
301+
<rule pattern="&#34;.*?&#34;">
302+
<token type="LiteralString"/>
303+
<pop depth="1"/>
304+
</rule>
305+
<rule pattern="&#39;.*?&#39;">
306+
<token type="LiteralString"/>
307+
<pop depth="1"/>
308+
</rule>
309+
<rule pattern="[^\s&gt;]+">
310+
<token type="LiteralString"/>
311+
<pop depth="1"/>
312+
</rule>
313+
</state>
314+
</rules>
315+
</lexer>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package views
2+
3+
templ Hello(name string) {
4+
<div>Hello, { name }</div>
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1

lexers/testdata/templ.actual

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package views
2+
3+
import "fmt"
4+
5+
var enabled = true
6+
7+
css primaryClassName(color string) {
8+
background-color: #ffffff;
9+
color: { color };
10+
}
11+
12+
script withParameters(a string, b string, c int) {
13+
console.log(a, b, c);
14+
}
15+
16+
templ UserCard(name string, count int, attrs templ.Attributes, contents templ.Component) {
17+
{{ first := name }}
18+
<section class={ "card", primaryClassName("red") } data-count={ count } noshade?={ enabled } @click={ handleClick } .active={ enabled } [data-kind]="card" #ref if enabled { disabled } for _, attr := range extraAttrs { { attr } } { "data-" + first }="paragraph" { attrs... }>
19+
if enabled {
20+
aria-label="enabled"
21+
}
22+
for _, label := range labels {
23+
data-label={ label }
24+
}
25+
<h1>{ name }</h1>
26+
<p>{ fmt.Sprintf("Count: %d", count) }</p>
27+
<script>
28+
const message = "Your message: {{ name }}";
29+
{{ name }}
30+
</script>
31+
if count > 0 {
32+
<ul>
33+
<li>active</li>
34+
</ul>
35+
} else {
36+
<p>inactive</p>
37+
}
38+
@Button("Save")
39+
@contents
40+
@wrapChildren() {
41+
<div>{ children... }</div>
42+
}
43+
</section>
44+
}

0 commit comments

Comments
 (0)