Skip to content

Commit da85333

Browse files
Yury SmolskyBryan C. Mills
authored andcommitted
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 <[email protected]> Reviewed-by: Andrew Bonventre <[email protected]>
1 parent f3bb33c commit da85333

File tree

4 files changed

+107
-4
lines changed

4 files changed

+107
-4
lines changed

edit.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
'shareURLEl': '#shareURL',
3131
{{end}}
3232
'enableHistory': true,
33-
'enableShortcuts': true
33+
'enableShortcuts': true,
34+
'enableVet': true
3435
});
3536
playgroundEmbed({
3637
'codeEl': '#code',

sandbox.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (s *server) handleCompile(w http.ResponseWriter, r *http.Request) {
6262
}
6363

6464
resp := &response{}
65-
key := cacheKey(req.Body)
65+
key := cacheKey("prog", req.Body)
6666
if err := s.cache.Get(key, resp); err != nil {
6767
if err != memcache.ErrCacheMiss {
6868
s.log.Errorf("s.cache.Get(%q, &response): %v", key, err)
@@ -89,10 +89,10 @@ func (s *server) handleCompile(w http.ResponseWriter, r *http.Request) {
8989
}
9090
}
9191

92-
func cacheKey(body string) string {
92+
func cacheKey(prefix, body string) string {
9393
h := sha256.New()
9494
io.WriteString(h, body)
95-
return fmt.Sprintf("prog-%s-%x", runtime.Version(), h.Sum(nil))
95+
return fmt.Sprintf("%s-%s-%x", prefix, runtime.Version(), h.Sum(nil))
9696
}
9797

9898
// isTestFunc tells whether fn has the type of a testing function.

server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func newServer(options ...func(s *server) error) (*server, error) {
5050
func (s *server) init() {
5151
s.mux.HandleFunc("/", s.handleEdit)
5252
s.mux.HandleFunc("/fmt", handleFmt)
53+
s.mux.HandleFunc("/vet", s.handleVet)
5354
s.mux.HandleFunc("/share", s.handleShare)
5455
s.mux.HandleFunc("/compile", s.handleCompile)
5556
s.mux.HandleFunc("/playground.js", s.handlePlaygroundJS)

vet.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"bytes"
9+
"encoding/json"
10+
"fmt"
11+
"io"
12+
"io/ioutil"
13+
"net/http"
14+
"os"
15+
"os/exec"
16+
"path/filepath"
17+
"strings"
18+
19+
"github.com/bradfitz/gomemcache/memcache"
20+
)
21+
22+
// handleVet performs a vet check on source code, trying cache for results first.
23+
// It serves an empty response if no errors were found by the "vet" tool.
24+
func (s *server) handleVet(w http.ResponseWriter, r *http.Request) {
25+
// TODO(ysmolsky): refactor common code in this function and handleCompile.
26+
// See golang.org/issue/24535.
27+
var req request
28+
// Until programs that depend on golang.org/x/tools/godoc/static/playground.js
29+
// are updated to always send JSON, this check is in place.
30+
if b := r.FormValue("body"); b != "" {
31+
req.Body = b
32+
} else if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
33+
s.log.Errorf("error decoding request: %v", err)
34+
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
35+
return
36+
}
37+
38+
resp := &response{}
39+
key := cacheKey("vet", req.Body)
40+
if err := s.cache.Get(key, resp); err != nil {
41+
if err != memcache.ErrCacheMiss {
42+
s.log.Errorf("s.cache.Get(%q, &response): %v", key, err)
43+
}
44+
resp, err = s.vetCheck(&req)
45+
if err != nil {
46+
s.log.Errorf("error checking vet: %v", err)
47+
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
48+
return
49+
}
50+
if err := s.cache.Set(key, resp); err != nil {
51+
s.log.Errorf("cache.Set(%q, resp): %v", key, err)
52+
}
53+
}
54+
55+
var buf bytes.Buffer
56+
if err := json.NewEncoder(&buf).Encode(resp); err != nil {
57+
s.log.Errorf("error encoding response: %v", err)
58+
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
59+
return
60+
}
61+
if _, err := io.Copy(w, &buf); err != nil {
62+
s.log.Errorf("io.Copy(w, &buf): %v", err)
63+
return
64+
}
65+
}
66+
67+
// vetCheck runs the "vet" tool on the source code in req.Body.
68+
// In case of no errors it returns an empty, non-nil *response.
69+
// Otherwise &response.Errors contains found errors.
70+
func (s *server) vetCheck(req *request) (*response, error) {
71+
tmpDir, err := ioutil.TempDir("", "vet")
72+
if err != nil {
73+
return nil, fmt.Errorf("error creating temp directory: %v", err)
74+
}
75+
defer os.RemoveAll(tmpDir)
76+
77+
in := filepath.Join(tmpDir, "main.go")
78+
if err := ioutil.WriteFile(in, []byte(req.Body), 0400); err != nil {
79+
return nil, fmt.Errorf("error creating temp file %q: %v", in, err)
80+
}
81+
82+
cmd := exec.Command("go", "vet", in)
83+
out, err := cmd.CombinedOutput()
84+
if err == nil {
85+
return &response{}, nil
86+
}
87+
88+
if _, ok := err.(*exec.ExitError); !ok {
89+
return nil, fmt.Errorf("error vetting go source: %v", err)
90+
}
91+
92+
// Rewrite compiler errors to refer to progName
93+
// instead of '/tmp/sandbox1234/main.go'.
94+
errs := strings.Replace(string(out), in, progName, -1)
95+
96+
// "go vet", invoked with a file name, puts this odd
97+
// message before any compile errors; strip it.
98+
errs = strings.Replace(errs, "# command-line-arguments\n", "", 1)
99+
100+
return &response{Errors: errs}, nil
101+
}

0 commit comments

Comments
 (0)