Skip to content

Commit c8ceaf6

Browse files
committed
Add dart formatter with CLIs or goja
0 parents  commit c8ceaf6

File tree

18 files changed

+24172
-0
lines changed

18 files changed

+24172
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 go-generalize
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

cli.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package dartfmt
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os/exec"
7+
"strings"
8+
)
9+
10+
func runCLI(script string, name string, arg ...string) (string, error) {
11+
cmd := exec.Command(name, arg...)
12+
cmd.Stdin = strings.NewReader(script)
13+
14+
stdout, stderr := bytes.Buffer{}, bytes.Buffer{}
15+
cmd.Stdout = &stdout
16+
cmd.Stderr = &stderr
17+
18+
if err := cmd.Start(); err != nil {
19+
return "", unavailableError{err: err}
20+
}
21+
22+
if err := cmd.Wait(); err != nil {
23+
return "", fmt.Errorf(
24+
"formatter failed(%s): %w",
25+
stdout.String()+"\n"+stderr.String(),
26+
err,
27+
)
28+
}
29+
30+
return stdout.String(), nil
31+
}
32+
33+
// FormatDartWithDartCLI formats a script with `dart format`
34+
func FormatDartWithDartCLI(script string) (string, error) {
35+
formatted, err := runCLI(script, "dart", "format")
36+
37+
if err != nil {
38+
return "", fmt.Errorf("formatting with `dart format` failed: %w", err)
39+
}
40+
41+
return formatted, nil
42+
}
43+
44+
// FormatDartWithDartfmtCLI formats a script with `dartfmt`
45+
func FormatDartWithDartfmtCLI(script string) (string, error) {
46+
formatted, err := runCLI(script, "dartfmt")
47+
48+
if err != nil {
49+
return "", fmt.Errorf("formatting with `dartfmt` failed: %w", err)
50+
}
51+
52+
return formatted, nil
53+
}
54+
55+
// FormatDartWithFlutterCLI formats a script with `flutter format`
56+
func FormatDartWithFlutterCLI(script string) (string, error) {
57+
formatted, err := runCLI(script, "flutter", "format")
58+
59+
if err != nil {
60+
return "", fmt.Errorf("formatting with `flutter format` failed: %w", err)
61+
}
62+
63+
return formatted, nil
64+
}

dartfmt.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package dartfmt
2+
3+
import (
4+
_ "embed"
5+
"errors"
6+
"fmt"
7+
)
8+
9+
// Formatter is a type of dart formatters
10+
type Formatter func(script string) (string, error)
11+
12+
// Formatters are candidates for FormatDart
13+
var Formatters = []Formatter{
14+
FormatDartWithFlutterCLI,
15+
FormatDartWithDartCLI,
16+
FormatDartWithDartfmtCLI,
17+
FormatDartStandalone,
18+
}
19+
20+
// FormatDart tries to format a script with all of Formatters one-by-one
21+
func FormatDart(script string) (string, error) {
22+
for _, fmtr := range Formatters {
23+
formatted, err := fmtr(script)
24+
25+
if err != nil {
26+
if errors.As(err, &unavailableError{}) {
27+
continue
28+
}
29+
30+
return "", fmt.Errorf("formatting failed: %w", err)
31+
}
32+
33+
return formatted, nil
34+
}
35+
36+
return "", fmt.Errorf("no available formatter")
37+
}

dartfmt_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dartfmt_test
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/go-generalize/go-dartfmt"
9+
"github.com/google/go-cmp/cmp"
10+
)
11+
12+
func runFormat(t *testing.T, formatter dartfmt.Formatter, dir string) {
13+
t.Helper()
14+
15+
input, err := os.ReadFile(filepath.Join(dir, "input.dart"))
16+
17+
if err != nil {
18+
t.Fatal(err)
19+
}
20+
21+
expectedBytes, err := os.ReadFile(filepath.Join(dir, "output.dart"))
22+
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
expected := string(expectedBytes)
27+
28+
actual, err := dartfmt.FormatDart(string(input))
29+
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
if diff := cmp.Diff(expected, actual); diff != "" {
35+
t.Errorf("the result differed: %s", diff)
36+
}
37+
}
38+
39+
func TestDartFormat(t *testing.T) {
40+
runFormat(t, dartfmt.FormatDart, "testdata/001")
41+
}

error.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dartfmt
2+
3+
type unavailableError struct {
4+
err error
5+
}
6+
7+
var _ error = unavailableError{}
8+
9+
func (e unavailableError) Error() string {
10+
return e.err.Error()
11+
}
12+
13+
func (e unavailableError) Unwrap() error {
14+
return e.err
15+
}
16+
17+
func (e unavailableError) Is(target error) bool {
18+
return target == e.err
19+
}

go.mod

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module github.com/go-generalize/go-dartfmt
2+
3+
go 1.17
4+
5+
require (
6+
github.com/dop251/goja v0.0.0-20220104154337-b93494d0c5d9
7+
github.com/google/go-cmp v0.5.6
8+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
9+
)
10+
11+
require (
12+
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
13+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
14+
golang.org/x/text v0.3.6 // indirect
15+
)

go.sum

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
3+
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
4+
github.com/dop251/goja v0.0.0-20220104154337-b93494d0c5d9 h1:QYh60R7QllMj/JKMtE2hCrwRqDbQq8vsUD63SDdVpHw=
5+
github.com/dop251/goja v0.0.0-20220104154337-b93494d0c5d9/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
6+
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
7+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
8+
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
9+
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
10+
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
11+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
12+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
13+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
14+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
15+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
16+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
17+
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
18+
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
19+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
20+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
21+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
22+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
23+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
24+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
25+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

goja.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package dartfmt
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"sync"
7+
8+
"github.com/dop251/goja"
9+
"github.com/dop251/goja/parser"
10+
"golang.org/x/sync/singleflight"
11+
)
12+
13+
//go:embed internal/entrypoint/dist/entrypoint.js
14+
var formatScript string
15+
var formatProgram *goja.Program
16+
17+
const formatScriptFormat = `
18+
var self = {location: {href: '/'}};
19+
%s
20+
var formatDart = self.formatDart;
21+
`
22+
23+
func compileFormatterForSingleflight() (interface{}, error) {
24+
if formatProgram != nil {
25+
return formatProgram, nil
26+
}
27+
28+
prog, err := goja.Parse(
29+
"entrypoint.js",
30+
fmt.Sprintf(formatScriptFormat, formatScript),
31+
parser.WithDisableSourceMaps,
32+
)
33+
34+
if err != nil {
35+
return nil, fmt.Errorf("parsing a script failed: %w", err)
36+
}
37+
38+
formatProgram, err = goja.CompileAST(prog, false)
39+
40+
if err != nil {
41+
return nil, fmt.Errorf("compiling AST failed: %w", err)
42+
}
43+
44+
return formatProgram, nil
45+
}
46+
47+
var group singleflight.Group
48+
var pool = sync.Pool{
49+
New: func() interface{} {
50+
fg, err, _ := group.Do("entrypoint.js", compileFormatterForSingleflight)
51+
52+
if err != nil {
53+
panic(err)
54+
}
55+
56+
vm := goja.New()
57+
58+
_, err = vm.RunProgram(fg.(*goja.Program))
59+
60+
if err != nil {
61+
panic(err)
62+
}
63+
64+
return vm
65+
},
66+
}
67+
68+
// FormatDartStandalone formats a script without dependencies on external commands
69+
func FormatDartStandalone(script string) (string, error) {
70+
vm := pool.Get().(*goja.Runtime)
71+
defer pool.Put(vm)
72+
73+
dartFormat := vm.GlobalObject().Get("formatDart")
74+
75+
if dartFormat == nil {
76+
return "", fmt.Errorf("formatDart is nil")
77+
}
78+
79+
var fn func(string) string
80+
err := vm.ExportTo(dartFormat, &fn)
81+
if err != nil {
82+
return "", fmt.Errorf("failed to execute dart_style formatter: %w", err)
83+
}
84+
85+
return fn(script), nil
86+
}

internal/entrypoint/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Files and directories created by pub
2+
.dart_tool/
3+
.packages
4+
5+
# Conventional directory for build outputs
6+
build/
7+
8+
# Directory created by dartdoc
9+
doc/api/

internal/entrypoint/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.PHONY: init build
2+
init:
3+
dart pub get
4+
5+
build:
6+
dart2js -O2 -o dist/entrypoint.js bin/entrypoint.dart

0 commit comments

Comments
 (0)