Skip to content

Commit 3ff06f9

Browse files
committed
docs: add CONTRIBUTING.md guide
1 parent d099072 commit 3ff06f9

File tree

1 file changed

+244
-0
lines changed

1 file changed

+244
-0
lines changed

CONTRIBUTING.md

+244
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# Contributing to templ
2+
3+
## Vision
4+
5+
Enable Go developers to build strongly typed, component-based HTML user interfaces with first-class developer tooling, and a short learning curve.
6+
7+
## Come up with a design and share it
8+
9+
Before starting work on any major pull requests or code changes, start a discussion at https://github.com/a-h/templ/discussions or raise an issue.
10+
11+
We don't want you to spend time on a PR or feature that ultimately doesn't get merged because it doesn't fit with the project goals, or the design doesn't work for some reason.
12+
13+
For issues, it really helps if you provide a reproduction repo, or can create a failing unit test to describe the behaviour.
14+
15+
In designs, we need to consider:
16+
17+
* Backwards compatibility - Not changing the public API between releases, introducing gradual deprecation - don't break people's code.
18+
* Correctness over time - How can we reduce the risk of defects both now, and in future releases?
19+
* Threat model - How could each change be used to inject vulnerabilities into web pages?
20+
* Go version - We target the oldest supported version of Go as per https://go.dev/doc/devel/release
21+
* Automatic migration - If we need to force through a change.
22+
* Compile time vs runtime errors - Prefer compile time.
23+
* Documentation - New features are only useful if people can understand the new feature, what would the documentation look like?
24+
* Examples - How will we demonstrate the feature?
25+
26+
## Project structure
27+
28+
templ is structured into a few areas:
29+
30+
### Parser `./parser`
31+
32+
The parser directory currently contains both v1 and v2 parsers.
33+
34+
The v1 parser is not maintained, it's only used to migrate v1 code over to the v2 syntax.
35+
36+
The parser is responsible for parsing templ files into an object model. The types that make up the object model are in `types.go`. Automatic formatting of the types is tested in `types_test.go`.
37+
38+
A templ file is parsed into the `TemplateFile` struct object model.
39+
40+
```go
41+
type TemplateFile struct {
42+
// Header contains comments or whitespace at the top of the file.
43+
Header []GoExpression
44+
// Package expression.
45+
Package Package
46+
// Nodes in the file.
47+
Nodes []TemplateFileNode
48+
}
49+
```
50+
51+
Parsers are individually tested using two types of unit test.
52+
53+
One test covers the successful parsing of text into an object. For example, the `HTMLCommentParser` test checks for successful patterns.
54+
55+
```go
56+
func TestHTMLCommentParser(t *testing.T) {
57+
var tests = []struct {
58+
name string
59+
input string
60+
expected HTMLComment
61+
}{
62+
{
63+
name: "comment - single line",
64+
input: `<!-- single line comment -->`,
65+
expected: HTMLComment{
66+
Contents: " single line comment ",
67+
},
68+
},
69+
{
70+
name: "comment - no whitespace",
71+
input: `<!--no whitespace between sequence open and close-->`,
72+
expected: HTMLComment{
73+
Contents: "no whitespace between sequence open and close",
74+
},
75+
},
76+
{
77+
name: "comment - multiline",
78+
input: `<!-- multiline
79+
comment
80+
-->`,
81+
expected: HTMLComment{
82+
Contents: ` multiline
83+
comment
84+
`,
85+
},
86+
},
87+
{
88+
name: "comment - with tag",
89+
input: `<!-- <p class="test">tag</p> -->`,
90+
expected: HTMLComment{
91+
Contents: ` <p class="test">tag</p> `,
92+
},
93+
},
94+
{
95+
name: "comments can contain tags",
96+
input: `<!-- <div> hello world </div> -->`,
97+
expected: HTMLComment{
98+
Contents: ` <div> hello world </div> `,
99+
},
100+
},
101+
}
102+
for _, tt := range tests {
103+
tt := tt
104+
t.Run(tt.name, func(t *testing.T) {
105+
input := parse.NewInput(tt.input)
106+
result, ok, err := htmlComment.Parse(input)
107+
if err != nil {
108+
t.Fatalf("parser error: %v", err)
109+
}
110+
if !ok {
111+
t.Fatalf("failed to parse at %d", input.Index())
112+
}
113+
if diff := cmp.Diff(tt.expected, result); diff != "" {
114+
t.Errorf(diff)
115+
}
116+
})
117+
}
118+
}
119+
```
120+
121+
Alongside each success test, is a similar test to check that invalid syntax is detected.
122+
123+
```go
124+
func TestHTMLCommentParserErrors(t *testing.T) {
125+
var tests = []struct {
126+
name string
127+
input string
128+
expected error
129+
}{
130+
{
131+
name: "unclosed HTML comment",
132+
input: `<!-- unclosed HTML comment`,
133+
expected: parse.Error("expected end comment literal '-->' not found",
134+
parse.Position{
135+
Index: 26,
136+
Line: 0,
137+
Col: 26,
138+
}),
139+
},
140+
{
141+
name: "comment in comment",
142+
input: `<!-- <-- other --> -->`,
143+
expected: parse.Error("comment contains invalid sequence '--'", parse.Position{
144+
Index: 8,
145+
Line: 0,
146+
Col: 8,
147+
}),
148+
},
149+
}
150+
for _, tt := range tests {
151+
tt := tt
152+
t.Run(tt.name, func(t *testing.T) {
153+
input := parse.NewInput(tt.input)
154+
_, _, err := htmlComment.Parse(input)
155+
if diff := cmp.Diff(tt.expected, err); diff != "" {
156+
t.Error(diff)
157+
}
158+
})
159+
}
160+
}
161+
```
162+
163+
### Generator
164+
165+
The generator takes the object model and writes out Go code that produces the expected output. Any changes to Go code output by templ are made in this area.
166+
167+
Testing of the generator is carried out by creating a templ file, and a matching expected output file.
168+
169+
For example, `./generator/test-a-href` contains a templ file of:
170+
171+
```templ
172+
package testahref
173+
174+
templ render() {
175+
<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
176+
<a href={ templ.URL("javascript:alert('should be sanitized')") }>Sanitized</a>
177+
<a href={ templ.SafeURL("javascript:alert('should not be sanitized')") }>Unsanitized</a>
178+
}
179+
```
180+
181+
It also contains an expected output file.
182+
183+
```html
184+
<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
185+
<a href="about:invalid#TemplFailedSanitizationURL">Sanitized</a>
186+
<a href="javascript:alert(&#39;should not be sanitized&#39;)">Unsanitized</a>
187+
```
188+
189+
These tests contribute towards the code coverage metrics by building an instrumented test CLI program. See the `test-cover` task in the `README.md` file.
190+
191+
### CLI
192+
193+
The command line interface for templ is used to generate Go code from templ files, format templ files, and run the LSP.
194+
195+
The code for this is at `./cmd/templ`.
196+
197+
Testing of the templ command line is done with unit tests to check the argument parsing.
198+
199+
The `templ generate` command is tested by generating templ files in the project, and testing that the expected output HTML is present.
200+
201+
### Runtime
202+
203+
The runtime is used by generated code, and by template authors, to serve template content over HTTP, and to carry out various operations.
204+
205+
It is in the root directory of the project at `./runtime.go`. The runtime is unit tested, as well as being tested as part of the `generate` tests.
206+
207+
### LSP
208+
209+
The LSP is structured within the command line interface, and proxies commands through to the `gopls` LSP.
210+
211+
### Docs
212+
213+
The docs are a Docusaurus project at `./docs`.
214+
215+
## Coding
216+
217+
### Build tasks
218+
219+
templ uses the `xc` task runner - https://github.com/joerdav/xc
220+
221+
If you run `xc` you can get see a list of the development tasks that can be run, or you can read the `README.md` file and see the `Tasks` section.
222+
223+
The most useful tasks for local development are:
224+
225+
* `install-snapshot` - this builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path.
226+
* `test` - this regenerates all templates, and runs the unit tests.
227+
* `fmt` - run the `gofmt` tool to format all Go code.
228+
* `lint` - run the same linting as run in the CI process.
229+
* `docs-run` - run the Docusaurus documentation site.
230+
231+
### Commit messages
232+
233+
The project using https://www.conventionalcommits.org/en/v1.0.0/
234+
235+
Examples:
236+
237+
* `feat: support Go comments in templates, fixes #234"`
238+
239+
### Coding style
240+
241+
* Reduce nesting - i.e. prefer early returns over an `else` block, as per https://danp.net/posts/reducing-go-nesting/ or https://go.dev/doc/effective_go#if
242+
* Use line breaks to separate "paragraphs" of code - don't use line breaks in between lines, or at the start/end of functions etc.
243+
* Use the `fmt` and `lint` build tasks to format and lint your code before submitting a PR.
244+

0 commit comments

Comments
 (0)