Skip to content

Commit 273e0ed

Browse files
authored
feat: format js and css with prettier if prettier is on the $PATH (#1230)
1 parent 431aa55 commit 273e0ed

File tree

198 files changed

+1449
-644
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

198 files changed

+1449
-644
lines changed

.github/copilot-instructions.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Coding standards
22

3+
## Behaviour
4+
5+
* Always run `go fmt` after making changes to Go code.
6+
* Always run unit tests after making changes to Go code.
7+
38
## Environment setup
49

510
* Ensure that the user has direnv installed, and that it is set up correctly in their shell. See https://direnv.net/docs/installation.html
@@ -25,13 +30,14 @@ The most useful tasks for local development are:
2530
* `xc install-snapshot` - builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path.
2631
* `xc generate` - generates Go code from the templ files in the project.
2732
* `xc test` - regenerates all templates, and runs the unit tests.
33+
* `xc test-short` - runs shorter tests, avoiding long running tests for filesystem watchers etc.
2834
* `xc fmt` - runs `gofmt` to format all Go code.
2935
* `xc lint` - run the same linting as run in the CI process.
3036
* `xc docs-run` - run the Docusaurus documentation site.
3137

3238
templ has a code generation step, this is automatically carried out using `xc test`.
3339

34-
Don't install templ globally using `xc install-snapshot` or `go install`. Use the `xc generate` or `xc test` tasks to generate the code, which will also run the tests.
40+
Don't install templ globally using `xc install-snapshot` or `go install`. Use the `xc generate` or `xc test-short` tasks to generate the code, which will also run the tests.
3541

3642
## Commit messages
3743

@@ -63,6 +69,7 @@ Examples:
6369
* Use the `xc fmt` and `xc lint` build tasks to format and lint code before committing.
6470
* Don't use unnecessary comments that explain what the code does.
6571
* If comments are used, ensure that they are full sentences, and use proper punctuation, including ending with a full stop.
72+
* Don't write comments after the end of keywords, e.g. `continue // Only process pairs`
6673

6774
## Tests
6875

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.935
1+
0.3.935

cmd/templ/fmtcmd/main.go

Lines changed: 44 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,66 @@ import (
88
"log/slog"
99
"os"
1010
"runtime"
11-
"sync"
1211
"time"
1312

14-
"github.com/a-h/templ/cmd/templ/imports"
1513
"github.com/a-h/templ/cmd/templ/processor"
16-
parser "github.com/a-h/templ/parser/v2"
14+
"github.com/a-h/templ/internal/format"
1715
"github.com/natefinch/atomic"
1816
)
1917

2018
type Arguments struct {
21-
FailIfChanged bool
22-
ToStdout bool
23-
StdinFilepath string
24-
Files []string
25-
WorkerCount int
19+
FailIfChanged bool
20+
ToStdout bool
21+
StdinFilepath string
22+
Files []string
23+
WorkerCount int
24+
PrettierCommand string
25+
PrettierRequired bool
2626
}
2727

2828
func Run(log *slog.Logger, stdin io.Reader, stdout io.Writer, args Arguments) (err error) {
2929
// If no files are provided, read from stdin and write to stdout.
30+
formatterConfig := format.Config{
31+
PrettierCommand: args.PrettierCommand,
32+
PrettierRequired: args.PrettierRequired,
33+
}
3034
if len(args.Files) == 0 {
31-
out, _ := format(writeToWriter(stdout), readFromReader(stdin, args.StdinFilepath), true)
32-
return out
35+
src, err := io.ReadAll(stdin)
36+
if err != nil {
37+
return fmt.Errorf("failed to read from stdin: %w", err)
38+
}
39+
formatted, _, err := format.Templ(src, args.StdinFilepath, formatterConfig)
40+
if err != nil {
41+
return fmt.Errorf("failed to format stdin: %w", err)
42+
}
43+
if _, err = stdout.Write(formatted); err != nil {
44+
return fmt.Errorf("failed to write to stdout: %w", err)
45+
}
46+
return nil
3347
}
48+
// If files are provided, process each file.
3449
process := func(fileName string) (error, bool) {
35-
read := readFromFile(fileName)
36-
write := writeToFile
50+
src, err := os.ReadFile(fileName)
51+
if err != nil {
52+
return fmt.Errorf("failed to read file %q: %w", fileName, err), false
53+
}
54+
formatted, changed, err := format.Templ(src, fileName, formatterConfig)
55+
if err != nil {
56+
return fmt.Errorf("failed to format file %q: %w", fileName, err), false
57+
}
58+
if !changed && !args.ToStdout {
59+
return nil, false
60+
}
3761
if args.ToStdout {
38-
write = writeToWriter(stdout)
62+
if _, err := stdout.Write(formatted); err != nil {
63+
return fmt.Errorf("failed to write to stdout: %w", err), false
64+
}
65+
return nil, true
66+
}
67+
if err := atomic.WriteFile(fileName, bytes.NewBuffer(formatted)); err != nil {
68+
return fmt.Errorf("failed to write file %q: %w", fileName, err), false
3969
}
40-
writeIfUnchanged := args.ToStdout
41-
return format(write, read, writeIfUnchanged)
70+
return nil, true
4271
}
4372
dir := args.Files[0]
4473
return NewFormatter(log, dir, process, args.WorkerCount, args.FailIfChanged).Run()
@@ -101,69 +130,3 @@ func (f *Formatter) Run() (err error) {
101130

102131
return nil
103132
}
104-
105-
type reader func() (fileName, src string, err error)
106-
107-
func readFromReader(r io.Reader, stdinFilepath string) func() (fileName, src string, err error) {
108-
return func() (fileName, src string, err error) {
109-
b, err := io.ReadAll(r)
110-
if err != nil {
111-
return "", "", fmt.Errorf("failed to read stdin: %w", err)
112-
}
113-
return stdinFilepath, string(b), nil
114-
}
115-
}
116-
117-
func readFromFile(name string) reader {
118-
return func() (fileName, src string, err error) {
119-
b, err := os.ReadFile(name)
120-
if err != nil {
121-
return "", "", fmt.Errorf("failed to read file %q: %w", fileName, err)
122-
}
123-
return name, string(b), nil
124-
}
125-
}
126-
127-
type writer func(fileName, tgt string) error
128-
129-
var mu sync.Mutex
130-
131-
func writeToWriter(w io.Writer) func(fileName, tgt string) error {
132-
return func(fileName, tgt string) error {
133-
mu.Lock()
134-
defer mu.Unlock()
135-
_, err := w.Write([]byte(tgt))
136-
return err
137-
}
138-
}
139-
140-
func writeToFile(fileName, tgt string) error {
141-
return atomic.WriteFile(fileName, bytes.NewBufferString(tgt))
142-
}
143-
144-
func format(write writer, read reader, writeIfUnchanged bool) (err error, fileChanged bool) {
145-
fileName, src, err := read()
146-
if err != nil {
147-
return err, false
148-
}
149-
t, err := parser.ParseString(src)
150-
if err != nil {
151-
return err, false
152-
}
153-
t.Filepath = fileName
154-
t, err = imports.Process(t)
155-
if err != nil {
156-
return err, false
157-
}
158-
w := new(bytes.Buffer)
159-
if err = t.Write(w); err != nil {
160-
return fmt.Errorf("formatting error: %w", err), false
161-
}
162-
163-
fileChanged = (src != w.String())
164-
165-
if !writeIfUnchanged && !fileChanged {
166-
return nil, fileChanged
167-
}
168-
return write(fileName, w.String()), fileChanged
169-
}

cmd/templ/lspcmd/proxy/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import (
1010
"strings"
1111

1212
"github.com/a-h/parse"
13+
"github.com/a-h/templ/internal/imports"
1314
"github.com/a-h/templ/internal/lazyloader"
1415
lsp "github.com/a-h/templ/lsp/protocol"
1516
"github.com/a-h/templ/lsp/uri"
1617

1718
"github.com/a-h/templ"
18-
"github.com/a-h/templ/cmd/templ/imports"
1919
"github.com/a-h/templ/generator"
2020
"github.com/a-h/templ/parser/v2"
2121
)

cmd/templ/main.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ Args:
172172
Set log verbosity level. (default "info", options: "debug", "info", "warn", "error")
173173
-w
174174
Number of workers to use when formatting code. (default runtime.NumCPUs).
175+
-prettier-command
176+
Set the command to use for formatting HTML, CSS, and JS blocks. Default is "prettier --stdin-filepath $TEMPL_PRETTIER_FILENAME".
177+
-prettier-required
178+
Set to true to return an error the prettier command is not available. Default is false.
175179
-fail
176180
Fails with exit code 1 if files are changed. (e.g. in CI)
177181
-help
@@ -185,6 +189,8 @@ func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int)
185189
verboseFlag := cmd.Bool("v", false, "")
186190
logLevelFlag := cmd.String("log-level", "info", "")
187191
failIfChanged := cmd.Bool("fail", false, "")
192+
prettierCommand := cmd.String("prettier-command", "", "")
193+
prettierRequired := cmd.Bool("prettier-required", false, "")
188194
stdoutFlag := cmd.Bool("stdout", false, "")
189195
stdinFilepath := cmd.String("stdin-filepath", "", "")
190196
err := cmd.Parse(args)
@@ -200,11 +206,13 @@ func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int)
200206
log := sloghandler.NewLogger(*logLevelFlag, *verboseFlag, stderr)
201207

202208
err = fmtcmd.Run(log, stdin, stdout, fmtcmd.Arguments{
203-
ToStdout: *stdoutFlag,
204-
Files: cmd.Args(),
205-
WorkerCount: *workerCountFlag,
206-
StdinFilepath: *stdinFilepath,
207-
FailIfChanged: *failIfChanged,
209+
ToStdout: *stdoutFlag,
210+
Files: cmd.Args(),
211+
WorkerCount: *workerCountFlag,
212+
StdinFilepath: *stdinFilepath,
213+
FailIfChanged: *failIfChanged,
214+
PrettierCommand: *prettierCommand,
215+
PrettierRequired: *prettierRequired,
208216
})
209217
if err != nil {
210218
return 1

cmd/templ/visualize/sourcemapvisualisation.templ

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ templ combine(templFileName string, left, right templ.Component) {
1919
<head>
2020
<title>{ templFileName }- Source Map Visualisation</title>
2121
<style type="text/css">
22-
.mapped { background-color: green }
23-
.highlighted { background-color: yellow }
22+
.mapped {
23+
background-color: green;
24+
}
25+
.highlighted {
26+
background-color: yellow;
27+
}
2428
</style>
2529
</head>
2630
<body>

cmd/templ/visualize/sourcemapvisualisation_templ.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/docs/03-syntax-and-usage/13-script-templates.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ templ body() {
1515
}
1616
```
1717

18+
If you have `prettierd`, `prettier` or `npx` on your `PATH`, `templ` will use it to format the `<script>` tag contents.
19+
1820
:::tip
1921
To ensure that a `<script>` tag within a templ component is only rendered once per HTTP response (or context), use a [templ.OnceHandle](18-render-once.md).
2022

docs/docs/09-developer-tools/01-cli.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ to exit with unix error-code `1` if any templates needed to be modified.
9494
templ fmt -fail .
9595
```
9696

97+
If `prettierd`, `prettier` or `npx` is found in your `PATH`, `templ fmt` will use prettier to format `script` and `style` elements in files.
98+
9799
## Language Server for IDE integration
98100

99101
`templ lsp` provides a Language Server Protocol (LSP) implementation to support IDE integrations.

docs/docs/09-developer-tools/02-ide-support.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ module.exports = {
145145

146146
With the templ LSP installed and configured, you can use the following code snippet to format on save:
147147

148-
149148
```lua
150149
vim.api.nvim_create_autocmd({ "BufWritePre" }, { pattern = { "*.templ" }, callback = vim.lsp.buf.format })
151150
```
@@ -211,6 +210,12 @@ local templ_format = function()
211210
end
212211
```
213212

213+
:::note
214+
Formatting `script` and `style` elements in templ files is handed off to prettier.
215+
216+
If you don't have `prettierd`, `prettier` or `npx` on your path, formatting will not be applied to those elements.
217+
:::
218+
214219
### Troubleshooting
215220

216221
If you cannot run `:TSInstall templ`, ensure you have an up-to-date version of [tree-sitter](https://github.com/nvim-treesitter/nvim-treesitter). The [package for templ](https://github.com/vrischmann/tree-sitter-templ) was [added to the main tree-sitter repository](https://github.com/nvim-treesitter/nvim-treesitter/pull/5667) so you shouldn't need to install a separate plugin for it.

0 commit comments

Comments
 (0)