playground: add vet
Playground runs a program through the "go vet" command and
displays found errors before the output of a program.
Vet check is performed for successfully compiled program only.
Vet errors are highlighted in the code editor.
Depends on the CL 107455.
Fixes golang/go#7597
Fixes golang/go#24576
Change-Id: I316a4a66e701f2a179aeafc82efc8fe31a543c11
Reviewed-on: https://go-review.googlesource.com/100776
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
diff --git a/edit.html b/edit.html
index 99fabd4..f3cd3be 100644
--- a/edit.html
+++ b/edit.html
@@ -30,7 +30,8 @@
'shareURLEl': '#shareURL',
{{end}}
'enableHistory': true,
- 'enableShortcuts': true
+ 'enableShortcuts': true,
+ 'enableVet': true
});
playgroundEmbed({
'codeEl': '#code',
diff --git a/sandbox.go b/sandbox.go
index 97d9b23..84c4b25 100644
--- a/sandbox.go
+++ b/sandbox.go
@@ -62,7 +62,7 @@
}
resp := &response{}
- key := cacheKey(req.Body)
+ key := cacheKey("prog", req.Body)
if err := s.cache.Get(key, resp); err != nil {
if err != memcache.ErrCacheMiss {
s.log.Errorf("s.cache.Get(%q, &response): %v", key, err)
@@ -89,10 +89,10 @@
}
}
-func cacheKey(body string) string {
+func cacheKey(prefix, body string) string {
h := sha256.New()
io.WriteString(h, body)
- return fmt.Sprintf("prog-%s-%x", runtime.Version(), h.Sum(nil))
+ return fmt.Sprintf("%s-%s-%x", prefix, runtime.Version(), h.Sum(nil))
}
// isTestFunc tells whether fn has the type of a testing function.
diff --git a/server.go b/server.go
index 2f6dab1..7035d1d 100644
--- a/server.go
+++ b/server.go
@@ -50,6 +50,7 @@
func (s *server) init() {
s.mux.HandleFunc("/", s.handleEdit)
s.mux.HandleFunc("/fmt", handleFmt)
+ s.mux.HandleFunc("/vet", s.handleVet)
s.mux.HandleFunc("/share", s.handleShare)
s.mux.HandleFunc("/compile", s.handleCompile)
s.mux.HandleFunc("/playground.js", s.handlePlaygroundJS)
diff --git a/vet.go b/vet.go
new file mode 100644
index 0000000..1dfa215
--- /dev/null
+++ b/vet.go
@@ -0,0 +1,101 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/bradfitz/gomemcache/memcache"
+)
+
+// handleVet performs a vet check on source code, trying cache for results first.
+// It serves an empty response if no errors were found by the "vet" tool.
+func (s *server) handleVet(w http.ResponseWriter, r *http.Request) {
+ // TODO(ysmolsky): refactor common code in this function and handleCompile.
+ // See golang.org/issue/24535.
+ var req request
+ // Until programs that depend on golang.org/x/tools/godoc/static/playground.js
+ // are updated to always send JSON, this check is in place.
+ if b := r.FormValue("body"); b != "" {
+ req.Body = b
+ } else if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ s.log.Errorf("error decoding request: %v", err)
+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+
+ resp := &response{}
+ key := cacheKey("vet", req.Body)
+ if err := s.cache.Get(key, resp); err != nil {
+ if err != memcache.ErrCacheMiss {
+ s.log.Errorf("s.cache.Get(%q, &response): %v", key, err)
+ }
+ resp, err = s.vetCheck(&req)
+ if err != nil {
+ s.log.Errorf("error checking vet: %v", err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ if err := s.cache.Set(key, resp); err != nil {
+ s.log.Errorf("cache.Set(%q, resp): %v", key, err)
+ }
+ }
+
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(resp); err != nil {
+ s.log.Errorf("error encoding response: %v", err)
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ return
+ }
+ if _, err := io.Copy(w, &buf); err != nil {
+ s.log.Errorf("io.Copy(w, &buf): %v", err)
+ return
+ }
+}
+
+// vetCheck runs the "vet" tool on the source code in req.Body.
+// In case of no errors it returns an empty, non-nil *response.
+// Otherwise &response.Errors contains found errors.
+func (s *server) vetCheck(req *request) (*response, error) {
+ tmpDir, err := ioutil.TempDir("", "vet")
+ if err != nil {
+ return nil, fmt.Errorf("error creating temp directory: %v", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ in := filepath.Join(tmpDir, "main.go")
+ if err := ioutil.WriteFile(in, []byte(req.Body), 0400); err != nil {
+ return nil, fmt.Errorf("error creating temp file %q: %v", in, err)
+ }
+
+ cmd := exec.Command("go", "vet", in)
+ out, err := cmd.CombinedOutput()
+ if err == nil {
+ return &response{}, nil
+ }
+
+ if _, ok := err.(*exec.ExitError); !ok {
+ return nil, fmt.Errorf("error vetting go source: %v", err)
+ }
+
+ // Rewrite compiler errors to refer to progName
+ // instead of '/tmp/sandbox1234/main.go'.
+ errs := strings.Replace(string(out), in, progName, -1)
+
+ // "go vet", invoked with a file name, puts this odd
+ // message before any compile errors; strip it.
+ errs = strings.Replace(errs, "# command-line-arguments\n", "", 1)
+
+ return &response{Errors: errs}, nil
+}