This commit is contained in:
parent
09fceca2f9
commit
40e5379eb3
11 changed files with 250 additions and 2 deletions
1
Makefile
1
Makefile
|
|
@ -15,6 +15,7 @@ build.wasm:
|
|||
GOOS=js GOARCH=wasm go build -o target/wasm/gotemplate.wasm ./cmds/gotemplate
|
||||
GOOS=js GOARCH=wasm go build -o target/wasm/timestamps.wasm ./cmds/timestamps
|
||||
GOOS=js GOARCH=wasm go build -o target/wasm/android-icons.wasm ./cmds/android-icons
|
||||
GOOS=js GOARCH=wasm go build -o target/wasm/golines.wasm ./cmds/golines
|
||||
cp $(GOROOT)/lib/wasm/wasm_exec.js target/wasm/.
|
||||
|
||||
.Phony: build.site
|
||||
|
|
|
|||
29
cmds/golines/format.go
Normal file
29
cmds/golines/format.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/golangci/golines/shorten"
|
||||
)
|
||||
|
||||
func formatSource(source string, maxLen int) (output []byte, err error) {
|
||||
// Dependency panics must not escape the JS callback and terminate the WASM runtime.
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
output = nil
|
||||
err = fmt.Errorf("golines could not format this input (internal panic: %v). Ensure the input is a complete Go file, including a package declaration", recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
tabLen := 4
|
||||
if maxLen < 80 {
|
||||
tabLen = 2
|
||||
}
|
||||
return shorten.NewShortener(&shorten.Config{
|
||||
MaxLen: maxLen,
|
||||
TabLen: tabLen,
|
||||
ShortenComments: true,
|
||||
ReformatTags: true,
|
||||
ChainSplitDots: true,
|
||||
}).Process([]byte(source))
|
||||
}
|
||||
45
cmds/golines/format_test.go
Normal file
45
cmds/golines/format_test.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatWidths(t *testing.T) {
|
||||
source := `package main
|
||||
func main() {
|
||||
println("first argument", "second argument", "third argument", "fourth argument")
|
||||
}
|
||||
`
|
||||
var narrow, wide string
|
||||
for width := 40; width <= 180; width += 5 {
|
||||
output, err := formatSource(source, width)
|
||||
if err != nil {
|
||||
t.Fatalf("width %d: %v", width, err)
|
||||
}
|
||||
if _, err := parser.ParseFile(token.NewFileSet(), "output.go", output, parser.AllErrors); err != nil {
|
||||
t.Fatalf("width %d produced invalid Go: %v", width, err)
|
||||
}
|
||||
if width == 40 {
|
||||
narrow = string(output)
|
||||
}
|
||||
if width == 180 {
|
||||
wide = string(output)
|
||||
}
|
||||
}
|
||||
if strings.Count(narrow, "\n") <= strings.Count(wide, "\n") {
|
||||
t.Fatal("narrow width did not split the call")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidSource(t *testing.T) {
|
||||
if _, err := formatSource("package main\nfunc {", 100); err == nil {
|
||||
t.Fatal("expected a syntax error")
|
||||
}
|
||||
// A failed edit must not prevent subsequent formatting.
|
||||
if _, err := formatSource("package main\nfunc main() {}", 100); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
40
cmds/golines/main.go
Normal file
40
cmds/golines/main.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//go:build js && wasm
|
||||
|
||||
package main
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
func main() {
|
||||
document := js.Global().Get("document")
|
||||
element := func(id string) js.Value { return document.Call("getElementById", id) }
|
||||
source, width := element("source"), element("max-len")
|
||||
output, message := element("output"), element("message")
|
||||
render := func() {
|
||||
maxLen := width.Get("valueAsNumber").Int()
|
||||
element("width-value").Set("textContent", maxLen)
|
||||
tabLen := 4
|
||||
if maxLen < 80 {
|
||||
tabLen = 2
|
||||
}
|
||||
element("tab-value").Set("textContent", tabLen)
|
||||
source.Get("style").Set("tabSize", tabLen)
|
||||
output.Get("style").Set("tabSize", tabLen)
|
||||
formatted, err := formatSource(source.Get("value").String(), maxLen)
|
||||
if err != nil {
|
||||
output.Set("value", "")
|
||||
message.Set("textContent", err.Error())
|
||||
source.Call("setAttribute", "aria-invalid", "true")
|
||||
return
|
||||
}
|
||||
source.Call("removeAttribute", "aria-invalid")
|
||||
output.Set("value", string(formatted))
|
||||
message.Set("textContent", "")
|
||||
}
|
||||
listener := js.FuncOf(func(this js.Value, args []js.Value) any { render(); return nil })
|
||||
source.Call("addEventListener", "input", listener)
|
||||
width.Call("addEventListener", "input", listener)
|
||||
source.Set("disabled", false)
|
||||
width.Set("disabled", false)
|
||||
render()
|
||||
select {}
|
||||
}
|
||||
25
cmds/golines/recovery_test.go
Normal file
25
cmds/golines/recovery_test.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatterPanicRecovery(t *testing.T) {
|
||||
// go/format accepts fragments, but dst v0.27.3 panics when golines
|
||||
// attempts to decorate this function without a package declaration.
|
||||
source := `func example() {
|
||||
println("first argument", "second argument", "third argument", "fourth argument")
|
||||
}`
|
||||
output, err := formatSource(source, 40)
|
||||
if err == nil || !strings.Contains(err.Error(), "internal panic") {
|
||||
t.Fatalf("expected recovered dependency panic, got output %q, error %v", output, err)
|
||||
}
|
||||
if output != nil {
|
||||
t.Fatalf("unexpected output after panic: %q", output)
|
||||
}
|
||||
output, err = formatSource("package main\n"+source, 40)
|
||||
if err != nil || !strings.Contains(string(output), "func example()") {
|
||||
t.Fatalf("formatting after panic failed: output %q, error %v", output, err)
|
||||
}
|
||||
}
|
||||
13
go.mod
13
go.mod
|
|
@ -3,6 +3,15 @@ module github.com/lmika/webtools
|
|||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/alecthomas/participle/v2 v2.1.4 // indirect
|
||||
golang.org/x/image v0.37.0 // indirect
|
||||
github.com/alecthomas/participle/v2 v2.1.4
|
||||
github.com/golangci/golines v0.15.0
|
||||
golang.org/x/image v0.37.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dave/dst v0.27.3 // indirect
|
||||
github.com/ldez/structtags v0.6.1 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
)
|
||||
|
|
|
|||
32
go.sum
32
go.sum
|
|
@ -1,4 +1,36 @@
|
|||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
|
||||
github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
|
||||
github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
|
||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0=
|
||||
github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk=
|
||||
github.com/ldez/structtags v0.6.1/go.mod h1:YDxVSgDy/MON6ariaxLF2X09bh19qL7MtGBN5MrvbdY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
|
||||
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
41
site/golines/index.html
Normal file
41
site/golines/index.html
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<title>Golines Playground - Tools</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body class="container">
|
||||
<header><hgroup>
|
||||
<h1>Golines Playground</h1>
|
||||
<p>Format Go code with <a href="https://github.com/golangci/golines">golines</a>. Everything runs in your browser.</p>
|
||||
</hgroup></header>
|
||||
<main>
|
||||
<label for="max-len">Maximum line width: <output id="width-value" for="max-len">100</output></label>
|
||||
<input id="max-len" type="range" min="40" max="180" step="5" value="100" disabled aria-describedby="config">
|
||||
<p id="config"><small>Tab width: <span id="tab-value">4</span>. Comment shortening, struct tag formatting, and splitting method chains at dots are enabled.</small></p>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<label for="source">Go source</label>
|
||||
<textarea id="source" spellcheck="false" autocapitalize="off" autocomplete="off" disabled aria-describedby="message">package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello from golines!", "Move the slider to explore how longer function calls are formatted.")
|
||||
}
|
||||
</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="output">Formatted output</label>
|
||||
<textarea id="output" readonly spellcheck="false" aria-label="Formatted output"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<p id="message" role="status" aria-live="polite">Loading formatter…</p>
|
||||
</main>
|
||||
<script src="main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
12
site/golines/main.js
Normal file
12
site/golines/main.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
try {
|
||||
await import("/wasm/wasm_exec.js");
|
||||
const go = new Go();
|
||||
const response = await fetch("/wasm/golines.wasm");
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const result = await WebAssembly.instantiate(await response.arrayBuffer(), go.importObject);
|
||||
await go.run(result.instance);
|
||||
} catch (error) {
|
||||
document.getElementById("message").textContent = `Unable to load the formatter: ${error.message}. Please reload to try again.`;
|
||||
document.getElementById("source").disabled = true;
|
||||
document.getElementById("max-len").disabled = true;
|
||||
}
|
||||
13
site/golines/style.css
Normal file
13
site/golines/style.css
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
textarea {
|
||||
height: 28rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
white-space: pre;
|
||||
overflow-wrap: normal;
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
#message {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
<ul>
|
||||
<li><a href="/clocks/">Clocks</a></li>
|
||||
<li><a href="/freelens-logo/">Freelens Logo Maker</a></li>
|
||||
<li><a href="/golines/">Golines Playground</a></li>
|
||||
<li><a href="/gotemplate/">Go Template Playground</a></li>
|
||||
<li><a href="/gradient-bands/">Gradient Bands</a></li>
|
||||
<li><a href="/2lcc/">Two-letter Country Codes</a></li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue