Skip to content

Commit

Permalink
playground: add vet
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Yury Smolsky authored and Bryan C. Mills committed May 8, 2018
1 parent f3bb33c commit da85333
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 4 deletions.
3 changes: 2 additions & 1 deletion edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
'shareURLEl': '#shareURL',
{{end}}
'enableHistory': true,
'enableShortcuts': true
'enableShortcuts': true,
'enableVet': true
});
playgroundEmbed({
'codeEl': '#code',
Expand Down
6 changes: 3 additions & 3 deletions sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (s *server) handleCompile(w http.ResponseWriter, r *http.Request) {
}

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)
Expand All @@ -89,10 +89,10 @@ func (s *server) handleCompile(w http.ResponseWriter, r *http.Request) {
}
}

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.
Expand Down
1 change: 1 addition & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func newServer(options ...func(s *server) error) (*server, error) {
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)
Expand Down
101 changes: 101 additions & 0 deletions vet.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit da85333

Please sign in to comment.