├── LICENSE
├── README.md
├── docs
└── expanderr.svg
├── expanderr.el
├── expanderr.go
├── expanderr_test.go
├── internal
└── srcimporter
│ └── srcimporter.go
├── lisp
└── go-expanderr.el
├── pos.go
├── screencast.gif
├── screencast.mp4
└── testdata
├── comment.got
└── src
│ └── comment
│ └── comment.go
├── comment.want
└── src
│ └── comment
│ └── comment.go
├── commentinline.got
└── src
│ └── commentinline
│ └── commentinline.go
├── commentinline.want
└── src
│ └── commentinline
│ └── commentinline.go
├── customtypes.got
└── src
│ └── customtypes
│ └── customtypes.go
├── customtypes.want
└── src
│ └── customtypes
│ └── customtypes.go
├── functionliteral.got
└── src
│ └── functionliteral
│ └── functionliteral.go
├── functionliteral.want
└── src
│ └── functionliteral
│ └── functionliteral.go
├── introduceerr.got
└── src
│ └── introduceerr
│ └── introduceerr.go
├── introduceerr.want
└── src
│ └── introduceerr
│ └── introduceerr.go
├── multipkg.got
└── src
│ ├── lib
│ └── lib.go
│ └── multipkg
│ └── multipkg.go
├── multipkg.want
└── src
│ └── multipkg
│ └── multipkg.go
├── multipkgvendor.got
└── src
│ └── multipkg
│ ├── multipkg.go
│ └── vendor
│ └── lib
│ └── lib.go
├── multipkgvendor.want
└── src
│ └── multipkg
│ └── multipkg.go
├── nocalleereturn.got
└── src
│ └── nocalleereturn
│ └── nocalleereturn.go
├── nocalleereturn.want
└── src
│ └── nocalleereturn
│ └── nocalleereturn.go
├── noerrreturn.got
└── src
│ └── noerrreturn
│ └── noerrreturn.go
├── noerrreturn.want
└── src
│ └── noerrreturn
│ └── noerrreturn.go
├── nointroduce.got
└── src
│ └── nointroduce
│ └── nointroduce.go
├── nointroduce.want
└── src
│ └── nointroduce
│ └── nointroduce.go
├── noreturncaller.got
└── src
│ └── noreturncaller
│ └── noreturncaller.go
├── noreturncaller.want
└── src
│ └── noreturncaller
│ └── noreturncaller.go
├── pkg.got
└── src
│ └── pkg
│ ├── pkg1.go
│ └── pkg2.go
├── pkg.want
└── src
│ └── pkg
│ └── pkg2.go
├── presentdouble.got
└── src
│ └── presentdouble
│ └── presentdouble.go
├── presentdouble.want
└── src
│ └── presentdouble
│ └── presentdouble.go
├── presentsingle.got
└── src
│ └── presentsingle
│ └── presentsingle.go
├── presentsingle.want
└── src
│ └── presentsingle
│ └── presentsingle.go
├── returnerrcall.got
└── src
│ └── returnerrcall
│ └── returnerrcall.go
├── returnerrcall.want
└── src
│ └── returnerrcall
│ └── returnerrcall.go
├── singleerror.got
└── src
│ └── singleerror
│ └── singleerror.go
├── singleerror.want
└── src
│ └── singleerror
│ └── singleerror.go
├── underscore.got
└── src
│ └── underscore
│ └── underscore.go
├── underscore.want
└── src
│ └── underscore
│ └── underscore.go
├── varanderror.got
└── src
│ └── varanderror
│ └── varanderror.go
└── varanderror.want
└── src
└── varanderror
└── varanderror.go
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # expanderr
2 |
3 | [](https://goreportcard.com/report/github.com/stapelberg/expanderr)
4 |
5 |
6 |
7 | The expanderr (think “expander”, pronounced with a pirate accent) is a tool
8 | which expands the Go Call Expression under your cursor to check errors. As an
9 | example, assuming your cursor is positioned on this call expression:
10 |
11 | ```go
12 | os.Remove("/tmp/state.bin")
13 | ```
14 |
15 | …invoking the expanderr will leave you with this If Statement instead:
16 |
17 | ```go
18 | if err := os.Remove("/tmp/state.bin"); err != nil {
19 | return err
20 | }
21 | ```
22 |
23 | Of course, the return values match the enclosing function signature, functions
24 | returning more than one argument are supported, and the local scope is
25 | considered to ensure that your code still compiles.
26 |
27 | 
28 |
29 | ## Setup
30 |
31 | Start by running `go get -u github.com/stapelberg/expanderr`. Then, follow the
32 | section for the editor you use:
33 |
34 | ### Emacs
35 |
36 | Add `(load "~/go/src/github.com/stapelberg/expanderr/lisp/go-expanderr.el")` to your Emacs configuration.
37 |
38 | From now on, use `C-c C-e` to invoke the expanderr.
39 |
40 | ## Opportunities to contribute
41 |
42 | * [vim integration](https://github.com/stapelberg/expanderr/issues/1)
43 | * use log.Fatal if within main()
44 | * integration for your favorite editor
45 | * [investigate support for the errors package](https://github.com/stapelberg/expanderr/issues/8)
46 |
47 | ## How does this differ from goreturns?
48 |
49 | goreturns only inserts a return statement with zero values for the current
50 | function.
51 |
52 | expanderr understands the signature of the call expression under your cursor and
53 | inserts the appropriate error checking statement (including a return
54 | statement). In practice, this eliminates the need of combining goreturns with an
55 | editor snippet, with the additional bonus of working correctly in a larger
56 | number of situations.
57 |
--------------------------------------------------------------------------------
/docs/expanderr.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
549 |
--------------------------------------------------------------------------------
/expanderr.el:
--------------------------------------------------------------------------------
1 | ;; this file provides backwards compatibilty with previously documented way of
2 | ;; loading this libray in the readme:
3 | ;; (load "~/go/src/github.com/stapelberg/expanderr/expanderr.el")
4 | (load-file (expand-file-name "lisp/go-expanderr.el" (file-name-directory load-file-name)))
5 |
--------------------------------------------------------------------------------
/expanderr.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Go Authors. All rights reserved.
2 | // Copyright 2014 The Go Authors. All rights reserved.
3 | // Copyright 2017 The Go Authors. All rights reserved.
4 | // Use of this source code is governed by a BSD-style
5 | // license that can be found in the LICENSE file.
6 |
7 | package main
8 |
9 | import (
10 | "bytes"
11 | "encoding/json"
12 | "errors"
13 | "flag"
14 | "fmt"
15 | "go/ast"
16 | "go/build"
17 | "go/format"
18 | "go/importer"
19 | "go/parser"
20 | "go/token"
21 | "go/types"
22 | "io"
23 | "io/ioutil"
24 | "log"
25 | "os"
26 | "path/filepath"
27 | "runtime/pprof"
28 | "strings"
29 | "unicode"
30 |
31 | "github.com/stapelberg/expanderr/internal/srcimporter"
32 |
33 | "golang.org/x/tools/go/ast/astutil"
34 | )
35 |
36 | var (
37 | unsafeFastImporter = flag.Bool("unsafe_fast_importer",
38 | false,
39 | "import installed packages when possible (unsafe until golang.org/issues/19337 is fixed)")
40 | )
41 |
42 | // parseQueryPos parses the source query position pos and returns the path
43 | // enclosing the specified interval.
44 | // (based on parseQueryPos from github.com/golang/tools/cmd/guru/guru.go)
45 | func parseQueryPos(fset *token.FileSet, root *ast.File, pos string, needExact bool) ([]ast.Node, error) {
46 | filename, startOffset, endOffset, err := parsePos(pos)
47 | if err != nil {
48 | return nil, err
49 | }
50 |
51 | // Find the named file among those in the loaded program.
52 | var file *token.File
53 | fset.Iterate(func(f *token.File) bool {
54 | if sameFile(filename, f.Name()) {
55 | file = f
56 | return false // done
57 | }
58 | return true // continue
59 | })
60 | if file == nil {
61 | return nil, fmt.Errorf("file %s not found in loaded program", filename)
62 | }
63 |
64 | // decrement startOffset as long as it points to |")", so that PathEnclosingInterval returns an ast.CallExpr
65 | //log.Printf("filename = %q, startOffset = %d, endOffset = %d\n", filename, startOffset, endOffset)
66 | b, err := ioutil.ReadFile(filename)
67 | if err != nil {
68 | return nil, err
69 | }
70 | //log.Printf("before: %q (rune: %v)", string(b[startOffset-5:endOffset+5]), rune(b[startOffset-1]))
71 | for unicode.IsSpace(rune(b[startOffset-1])) || b[startOffset-1] == ')' {
72 | startOffset--
73 | endOffset--
74 | //log.Printf("decremented to startOffset = %d, endOffset = %d\n", startOffset, endOffset)
75 | }
76 | //log.Printf("after: %q", string(b[startOffset-5:endOffset+5]))
77 |
78 | start, end, err := fileOffsetToPos(file, startOffset, endOffset)
79 | if err != nil {
80 | return nil, err
81 | }
82 |
83 | path, exact := astutil.PathEnclosingInterval(root, start, end)
84 | if path == nil {
85 | return nil, fmt.Errorf("no syntax here")
86 | }
87 | if needExact && !exact {
88 | return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
89 | }
90 | return path, nil
91 | }
92 |
93 | func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
94 |
95 | var errUnknownSignature = errors.New("unknown signature")
96 |
97 | func signatureOf(info *types.Info, e *ast.CallExpr) (*types.Signature, error) {
98 | // Deal with obviously static calls before constructing SSA form.
99 | // Some static calls may yet require SSA construction,
100 | // e.g. f := func(){}; f().
101 | switch funexpr := unparen(e.Fun).(type) {
102 | case *ast.Ident:
103 | switch obj := info.Uses[funexpr].(type) {
104 | case *types.Builtin:
105 | // Reject calls to built-ins.
106 | return nil, fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name())
107 | case *types.Func:
108 | // This is a static function call
109 | return obj.Type().(*types.Signature), nil
110 | case *types.Var:
111 | // This is a function literal call
112 | return obj.Type().(*types.Signature), nil
113 | default:
114 | // TODO: better error message: the function signature for could not be found
115 | //return nil, fmt.Errorf("unhandled: info.Uses[%v] = %T", funexpr, obj)
116 | return nil, errUnknownSignature
117 | }
118 | case *ast.SelectorExpr:
119 | sel := info.Selections[funexpr]
120 | if sel == nil {
121 | // qualified identifier.
122 | // May refer to top level function variable
123 | // or to top level function.
124 | switch callee := info.Uses[funexpr.Sel].(type) {
125 | case *types.Func:
126 | return callee.Type().(*types.Signature), nil
127 | default:
128 | // TODO: better error message (see above)
129 | return nil, errUnknownSignature
130 | }
131 | } else if sel.Kind() == types.MethodVal {
132 | // Inspect the receiver type of the selected method.
133 | // If it is concrete, the call is statically dispatched.
134 | // (Due to implicit field selections, it is not enough to look
135 | // at sel.Recv(), the type of the actual receiver expression.)
136 | method := sel.Obj().(*types.Func)
137 | return method.Type().(*types.Signature), nil
138 | }
139 | }
140 | return nil, fmt.Errorf("unhandled: signature of %T", unparen(e.Fun))
141 | }
142 |
143 | func currentSignature(path []ast.Node) (*ast.FuncType, error) {
144 | for _, n := range path {
145 | switch n := n.(type) {
146 | case *ast.FuncDecl:
147 | return n.Type, nil
148 | case *ast.FuncLit:
149 | return n.Type, nil
150 | }
151 | }
152 | return nil, fmt.Errorf("no function definition found in path")
153 | }
154 |
155 | // newZeroValueNode returns an AST expr representing the zero value of
156 | // typ. If determining the zero value requires additional information
157 | // (e.g., type-checking output), it returns nil.
158 | // (from github.com/sqs/goreturns/returns/fix.go)
159 | func newZeroValueNode(typ ast.Expr) ast.Expr {
160 | switch v := typ.(type) {
161 | case *ast.Ident:
162 | switch v.Name {
163 | case "uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "byte", "rune", "uint", "int", "uintptr":
164 | return &ast.BasicLit{Kind: token.INT, Value: "0"}
165 | case "float32", "float64":
166 | return &ast.BasicLit{Kind: token.FLOAT, Value: "0"}
167 | case "complex64", "complex128":
168 | return &ast.BasicLit{Kind: token.IMAG, Value: "0"}
169 | case "bool":
170 | return &ast.Ident{Name: "false"}
171 | case "string":
172 | return &ast.BasicLit{Kind: token.STRING, Value: `""`}
173 | case "error":
174 | return &ast.Ident{Name: "nil"}
175 | }
176 | case *ast.ArrayType:
177 | if v.Len == nil {
178 | // slice
179 | return &ast.Ident{Name: "nil"}
180 | }
181 | return &ast.CompositeLit{Type: v}
182 | case *ast.StarExpr:
183 | return &ast.Ident{Name: "nil"}
184 | }
185 | return nil
186 | }
187 |
188 | // Like newZeroValueNode, but with type information.
189 | //
190 | // TODO: can we safely get rid of newZeroValueNode or does it handle cases which
191 | // would otherwise go unhandled?
192 | func newZeroValueNodeTypeName(id *ast.Ident, name *types.TypeName) ast.Expr {
193 | switch t := name.Type().Underlying().(type) {
194 | case *types.Struct:
195 | return &ast.Ident{Name: id.Name + "{}"}
196 |
197 | case *types.Interface:
198 | return &ast.Ident{Name: "nil"}
199 |
200 | case *types.Basic:
201 | switch t.Kind() {
202 | case types.Int, types.Int8, types.Int16, types.Int32, types.Int64,
203 | types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.Uintptr:
204 | return &ast.BasicLit{Kind: token.INT, Value: "0"}
205 |
206 | case types.Float32, types.Float64:
207 | return &ast.BasicLit{Kind: token.FLOAT, Value: "0"}
208 |
209 | case types.Complex64, types.Complex128:
210 | return &ast.BasicLit{Kind: token.IMAG, Value: "0"}
211 |
212 | case types.Bool:
213 | return &ast.Ident{Name: "false"}
214 |
215 | case types.String:
216 | return &ast.BasicLit{Kind: token.STRING, Value: `""`}
217 | }
218 | }
219 | return nil
220 | }
221 |
222 | // fallbackImporter tries to import using importer first, falling back to
223 | // srcImporter on any error. This allows us to load binary files (significantly
224 | // faster) where possible, but import from source where necessary.
225 | type fallbackImporter struct {
226 | importer types.ImporterFrom
227 | srcImporter types.ImporterFrom
228 | }
229 |
230 | func (fi *fallbackImporter) Import(path string) (*types.Package, error) {
231 | p, err := fi.importer.Import(path)
232 | if err != nil {
233 | return fi.srcImporter.Import(path)
234 | }
235 | return p, err
236 | }
237 |
238 | func (fi *fallbackImporter) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) {
239 | p, err := fi.importer.ImportFrom(path, srcDir, mode)
240 | if err != nil {
241 | return fi.srcImporter.ImportFrom(path, srcDir, mode)
242 | }
243 | return p, err
244 | }
245 |
246 | func callExprAtPath(path []ast.Node) *ast.CallExpr {
247 | var ce *ast.CallExpr
248 | // Return the outer-most *ast.CallExpr in path, if any.
249 | for _, p := range path {
250 | if e, ok := p.(*ast.CallExpr); ok {
251 | ce = e
252 | }
253 | }
254 | if ce != nil {
255 | return ce
256 | }
257 |
258 | // Look for an *ast.CallExpr within the *ast.BlockStmt, if path starts with
259 | // an *ast.BlockStmt.
260 | if first, ok := path[0].(*ast.BlockStmt); ok {
261 | ast.Inspect(first, func(n ast.Node) bool {
262 | if n, ok := n.(*ast.CallExpr); ok {
263 | ce = n
264 | return false // found, stop
265 | }
266 |
267 | return true // recurse
268 | })
269 | if ce != nil {
270 | return ce
271 | }
272 | }
273 |
274 | return nil // no *ast.CallExpr found
275 | }
276 |
277 | func defaultImporter() types.Importer {
278 | // TODO(golang.org/issues/19337): default to fallbackImporter once packages
279 | // are augmented.
280 | if *unsafeFastImporter {
281 | return &fallbackImporter{
282 | importer: importer.Default().(types.ImporterFrom),
283 | srcImporter: importer.For("source", nil).(types.ImporterFrom),
284 | }
285 | }
286 | i := importer.For("source", nil)
287 | if i == nil {
288 | // Fallback for Go <1.9
289 | i = srcimporter.New(&build.Default, token.NewFileSet(), make(map[string]*types.Package))
290 | }
291 | return i
292 | }
293 |
294 | // expansion holds state during the error expansion.
295 | type expansion struct {
296 | fset *token.FileSet
297 | file *ast.File // the file under cursor
298 | ce *ast.CallExpr // the call expression under the cursor
299 | callee *types.Signature // the callee’s signature
300 | caller *ast.FuncType // the caller’s type (including signature)
301 | results []ast.Expr // return values for the new error check
302 | info *types.Info // type information of the type-checked package
303 | pkg *types.Package
304 | path []ast.Node // node under cursor and all its ancestors
305 | }
306 |
307 | func (e *expansion) getScope() *types.Scope {
308 | for _, p := range e.path {
309 | if funcDecl, ok := p.(*ast.FuncDecl); ok {
310 | if s, ok := e.info.Scopes[funcDecl.Type]; ok {
311 | return s
312 | }
313 | }
314 | if s, ok := e.info.Scopes[p]; ok {
315 | return s
316 | }
317 | }
318 | return nil
319 | }
320 |
321 | func errPresent(lhs []ast.Expr) bool {
322 | for _, expr := range lhs {
323 | if id, ok := expr.(*ast.Ident); ok && id.Name == "err" {
324 | return true
325 | }
326 | }
327 | return false
328 | }
329 |
330 | // parent returns the parent node of n within e.path.
331 | func (e *expansion) parent(n ast.Node) ast.Node {
332 | found := false
333 | for _, p := range e.path {
334 | if found {
335 | return p
336 | }
337 | found = (p == n)
338 | }
339 | return nil
340 | }
341 |
342 | func (e *expansion) typeCheck(pkgname string, files []*ast.File, addWarnFunc func(string)) error {
343 | e.info = &types.Info{
344 | Uses: make(map[*ast.Ident]types.Object),
345 | Selections: make(map[*ast.SelectorExpr]*types.Selection),
346 | Scopes: make(map[ast.Node]*types.Scope),
347 | }
348 |
349 | conf := types.Config{
350 | Importer: defaultImporter(),
351 | Error: func(err error) {
352 | addWarnFunc(fmt.Sprintf("ignoring type-checking error: %v", err))
353 | }, // keep going on errors
354 | }
355 | pkg, _ := conf.Check(pkgname, e.fset, files, e.info)
356 | // Type checking errors are ignored so that we can write expressions like
357 | // “n := io.Write(p)” (two values assigned to one variable).
358 |
359 | e.pkg = pkg
360 |
361 | var err error
362 | e.caller, err = currentSignature(e.path)
363 | if err != nil {
364 | return err
365 | }
366 |
367 | e.ce = callExprAtPath(e.path)
368 | if e.ce == nil {
369 | return fmt.Errorf("no ast.CallExpr found")
370 | }
371 | e.callee, err = signatureOf(e.info, e.ce)
372 | if err != nil {
373 | return err
374 | }
375 |
376 | if e.caller.Results == nil {
377 | return nil
378 | }
379 |
380 | e.results = make([]ast.Expr, len(e.caller.Results.List))
381 | for idx, res := range e.caller.Results.List {
382 | if id, ok := res.Type.(*ast.Ident); ok && id.Name == "error" {
383 | e.results[idx] = &ast.Ident{Name: "err"}
384 | } else {
385 | e.results[idx] = newZeroValueNode(res.Type)
386 | if e.results[idx] == nil {
387 | // We could not figure out from the AST what the type is, so
388 | // it’s not a builtin type, array type or pointer type.
389 | if id, ok := res.Type.(*ast.Ident); ok {
390 | if tn, ok := e.info.Uses[id].(*types.TypeName); ok {
391 | e.results[idx] = newZeroValueNodeTypeName(id, tn)
392 | }
393 | }
394 | }
395 | }
396 | }
397 | return nil
398 | }
399 |
400 | // this function either returns just the return values of the function or, if there are no errors
401 | // returned, will also add in the no-error-callback
402 | func (e *expansion) getFinalOutput(noReturnStr string, errName string) ([]ast.Stmt, error) {
403 | var normalReturn = &ast.ReturnStmt{Results: e.results}
404 |
405 | var returnsError = true
406 | if res := e.caller.Results; res == nil {
407 | returnsError = false
408 | } else if list := res.List; len(list) == 0 {
409 | returnsError = false
410 | } else if id, ok := list[len(list)-1].Type.(*ast.Ident); ok && id.Name != "error" {
411 | returnsError = false
412 | }
413 |
414 | if returnsError {
415 | return []ast.Stmt{normalReturn}, nil
416 | }
417 |
418 | if noReturnStr == "" { // default output when no error retuned
419 | return []ast.Stmt{&ast.ExprStmt{
420 | X: &ast.CallExpr{
421 | Fun: &ast.Ident{Name: "panic"},
422 | Args: []ast.Expr{
423 | &ast.Ident{Name: "err"},
424 | },
425 | },
426 | }}, nil
427 | }
428 |
429 | noReturnExpr, err := parser.ParseExpr(noReturnStr)
430 | if err != nil {
431 | return nil, fmt.Errorf("parsing template result into ast: %v", err)
432 | }
433 |
434 | return []ast.Stmt{
435 | &ast.ExprStmt{X: noReturnExpr},
436 | normalReturn,
437 | }, nil
438 | }
439 |
440 | func logic(w io.Writer, buildctx *build.Context, posn, noReturnStr string) error {
441 | e := expansion{
442 | fset: token.NewFileSet(),
443 | }
444 |
445 | // Short-cut: parse+type-check a single file before loading the entire
446 | // package.
447 | filename, _, _, err := parsePos(posn)
448 | if err != nil {
449 | return err
450 | }
451 |
452 | e.file, err = parser.ParseFile(e.fset, filename, nil, parser.ParseComments)
453 | if err != nil {
454 | return fmt.Errorf("parsing: %v", err)
455 | }
456 |
457 | e.path, err = parseQueryPos(e.fset, e.file, posn, false)
458 | if err != nil {
459 | return err
460 | }
461 |
462 | // TODO(golang.org/issues/21418): hack: importer.For always uses
463 | // build.Default, so we need to change build.Default
464 | build.Default = *buildctx
465 |
466 | var warnings []string
467 | var warnFunc = func(warning string) { warnings = append(warnings, warning) }
468 | if err := e.typeCheck("main", []*ast.File{e.file}, warnFunc); err != nil {
469 | if err != errUnknownSignature {
470 | return err
471 | }
472 |
473 | // Parse all files, type-check again.
474 | d, err := os.Open(filepath.Dir(filename))
475 | if err != nil {
476 | return err
477 | }
478 | defer d.Close()
479 | names, err := d.Readdirnames(-1)
480 | if err != nil {
481 | return err
482 | }
483 | files := []*ast.File{e.file}
484 | // TODO: parallelize
485 | for _, n := range names {
486 | if n == filepath.Base(filename) {
487 | continue // already parsed
488 | }
489 | if strings.HasPrefix(n, "expanderr") {
490 | continue // skip expanderr temp file when working in /tmp
491 | }
492 | if !strings.HasSuffix(n, ".go") {
493 | continue
494 | }
495 | f, err := parser.ParseFile(e.fset, filepath.Join(filepath.Dir(filename), n), nil, parser.ParseComments)
496 | if err != nil {
497 | return fmt.Errorf("parsing: %v", err)
498 | }
499 | files = append(files, f)
500 | }
501 | if err := e.typeCheck(e.pkg.Name(), files, warnFunc); err != nil {
502 | return err
503 | }
504 | }
505 |
506 | var subject ast.Node // what will be replaced
507 | subject = e.ce
508 | var repl []ast.Node
509 | switch e.callee.Results().Len() {
510 | case 0:
511 | // nothing to replace, i.e. keep the original *ast.CallExpr
512 | repl = []ast.Node{e.ce}
513 | case 1:
514 | // TODO: check if this CallExpr is within an AssignStmt. if so, replace the AssignStmt instead
515 | if parent := e.parent(subject); parent != nil {
516 | if stmt, ok := parent.(*ast.AssignStmt); ok {
517 | subject = stmt
518 | }
519 | }
520 |
521 | outputStmt, err := e.getFinalOutput(noReturnStr, "err")
522 | if err != nil {
523 | return err
524 | }
525 |
526 | // e.g. os.Remove(…) → if err := os.Remove(…); err != nil { return 0, err }
527 | repl = []ast.Node{&ast.IfStmt{
528 | Init: &ast.AssignStmt{
529 | Lhs: []ast.Expr{&ast.Ident{Name: "err"}},
530 | Tok: token.DEFINE,
531 | Rhs: []ast.Expr{e.ce},
532 | },
533 | Cond: &ast.BinaryExpr{
534 | X: &ast.Ident{Name: "err"},
535 | Op: token.NEQ,
536 | Y: &ast.Ident{Name: "nil"},
537 | },
538 | Body: &ast.BlockStmt{
539 | List: outputStmt,
540 | },
541 | }}
542 | default:
543 | // e.g. f := os.Create(…) → f, err := os.Create(…); if err != nil { return 0, err }
544 |
545 | // walk up to the *ast.AssignStmt and append an *ast.Ident to its .Lhs
546 | var as *ast.AssignStmt
547 | for _, p := range e.path {
548 | if p, ok := p.(*ast.AssignStmt); ok {
549 | as = p
550 | break
551 | }
552 | }
553 | if as == nil {
554 | return fmt.Errorf("no *ast.AssignStmt found in path") // TODO: better error msg
555 | }
556 |
557 | scope := e.getScope()
558 | if scope == nil {
559 | return fmt.Errorf("could not find scope") // TODO: better error msg. can this happen at all?
560 | }
561 | errInScope := scope.Lookup("err") != nil
562 |
563 | subject = as
564 |
565 | onlyUnderscore := true
566 | for _, lhs := range as.Lhs {
567 | switch lhs := lhs.(type) {
568 | case *ast.Ident:
569 | if lhs.Name != "_" {
570 | onlyUnderscore = false
571 | }
572 | default:
573 | onlyUnderscore = false
574 | }
575 | }
576 |
577 | // TODO: verify all other parameters are assigned
578 |
579 | if !errPresent(as.Lhs) {
580 | as.Lhs = append(as.Lhs, &ast.Ident{Name: "err"})
581 | }
582 |
583 | outputStmt, err := e.getFinalOutput(noReturnStr, "err")
584 | if err != nil {
585 | return err
586 | }
587 |
588 | if !onlyUnderscore && as.Tok == token.DEFINE {
589 | // Insert a new *ast.IfStmt after the *ast.CallExpr.
590 | repl = []ast.Node{
591 | subject,
592 | &ast.IfStmt{
593 | Cond: &ast.BinaryExpr{
594 | X: &ast.Ident{Name: "err"},
595 | Op: token.NEQ,
596 | Y: &ast.Ident{Name: "nil"},
597 | },
598 | Body: &ast.BlockStmt{
599 | List: outputStmt,
600 | },
601 | },
602 | }
603 | } else {
604 | tok := as.Tok
605 | if onlyUnderscore {
606 | tok = token.DEFINE
607 | }
608 | if !onlyUnderscore && !errInScope {
609 | // The “err” identifier is not yet in scope, so insert a “var
610 | // err error” declaration before the *ast.IfStmt.
611 | repl = append(repl, &ast.DeclStmt{
612 | Decl: &ast.GenDecl{
613 | Tok: token.VAR,
614 | Specs: []ast.Spec{
615 | &ast.ValueSpec{
616 | Names: []*ast.Ident{&ast.Ident{Name: "err"}},
617 | Type: &ast.Ident{Name: "error"},
618 | },
619 | },
620 | },
621 | })
622 | }
623 | // Embed the *ast.CallExpr in an *ast.IfStmt.
624 | repl = append(repl, &ast.IfStmt{
625 | Init: &ast.AssignStmt{
626 | Lhs: as.Lhs,
627 | Tok: tok,
628 | Rhs: []ast.Expr{e.ce},
629 | },
630 | Cond: &ast.BinaryExpr{
631 | X: &ast.Ident{Name: "err"},
632 | Op: token.NEQ,
633 | Y: &ast.Ident{Name: "nil"},
634 | },
635 | Body: &ast.BlockStmt{
636 | List: []ast.Stmt{
637 | &ast.ReturnStmt{Results: e.results},
638 | },
639 | },
640 | })
641 | }
642 | }
643 |
644 | // TODO(golang.org/issues/20744): switch from textual replacement to
645 | // formatting the AST once comments are represented in a more convenient
646 | // way.
647 |
648 | b, err := ioutil.ReadFile(filename)
649 | if err != nil {
650 | return err
651 | }
652 | var src bytes.Buffer
653 | // Copy everything before the subject as-is.
654 | if _, err := src.Write(b[:subject.Pos()-1]); err != nil {
655 | return err
656 | }
657 | var newEnd int
658 | // Print the replacement
659 | for _, node := range repl {
660 | // Hack: inline comments (e.g. os.Remove("/foo" /* path */)) get lost
661 | // when formatting node, so we string-replace the formatted CallExpr
662 | // with the original CallExpr.
663 |
664 | var stmtFmt, ceFmt bytes.Buffer
665 | if err := format.Node(&stmtFmt, token.NewFileSet(), node); err != nil {
666 | return fmt.Errorf("formatting replacement: %v", err)
667 | }
668 | if err := format.Node(&ceFmt, e.fset, e.ce); err != nil {
669 | return fmt.Errorf("formatting replacement: %v", err)
670 | }
671 |
672 | newEnd += len(strings.Split(stmtFmt.String(), "\n"))
673 |
674 | ceOrig := string(b[e.ce.Pos()-1 : e.ce.End()-1])
675 | if _, err := src.Write([]byte(strings.Replace(stmtFmt.String(), ceFmt.String(), ceOrig, 1))); err != nil {
676 | return err
677 | }
678 |
679 | if _, err := src.Write([]byte(";")); err != nil {
680 | return err
681 | }
682 | }
683 | // Copy everything after the subject as-is.
684 | if _, err := src.Write(b[subject.End():]); err != nil {
685 | return err
686 | }
687 |
688 | // Format the entire source code — formatting the replacement is not
689 | // sufficient, as the replacement is not formatted in context.
690 | formatted, err := format.Source(src.Bytes())
691 | if err != nil {
692 | return fmt.Errorf("formatting source: %v.\nsource:\n%s", err, src.String())
693 | }
694 |
695 | if *formatFlag == "json" {
696 | start := e.fset.Position(subject.Pos()).Line
697 | end := e.fset.Position(subject.End()).Line
698 | var lines []string
699 | for idx, line := range strings.Split(string(formatted), "\n") {
700 | if idx >= start-1 && idx < start-1+newEnd {
701 | lines = append(lines, line)
702 | }
703 | }
704 | if err := json.NewEncoder(w).Encode(struct {
705 | Start int `json:"start"`
706 | End int `json:"end"`
707 | Lines []string `json:"lines"`
708 | Warnings []string `json:"warnings"`
709 | }{
710 | Start: start,
711 | End: end,
712 | Lines: lines,
713 | Warnings: warnings,
714 | }); err != nil {
715 | return err
716 | }
717 | } else {
718 | for _, w := range warnings {
719 | log.Print(w)
720 | }
721 | if _, err := w.Write(formatted); err != nil {
722 | return err
723 | }
724 | }
725 |
726 | return nil
727 | }
728 |
729 | var (
730 | wFlag = flag.String("w", "", "write")
731 | formatFlag = flag.String("format", "", "output format (source, json). defaults to 'source'")
732 | cpuprofile = flag.String("cpuprofile", "", "write cpu profile `file`")
733 | noErrReturnStr = flag.String("no-error-callback", "", "function call to be used if there is no error return value. ex: 'log.Fatalf(\"boom: %v\", err)'. defaults to 'panic(err)'")
734 | )
735 |
736 | func main() {
737 | flag.Parse()
738 |
739 | if *cpuprofile != "" {
740 | f, err := os.Create(*cpuprofile)
741 | if err != nil {
742 | log.Fatal("could not create CPU profile: ", err)
743 | }
744 | if err := pprof.StartCPUProfile(f); err != nil {
745 | log.Fatal("could not start CPU profile: ", err)
746 | }
747 | defer pprof.StopCPUProfile()
748 | }
749 |
750 | args := flag.Args()
751 | if len(args) != 1 {
752 | flag.Usage()
753 | os.Exit(2)
754 | }
755 | posn := args[0]
756 |
757 | o := io.Writer(os.Stdout)
758 | if *wFlag != "" {
759 | f, err := os.Create(*wFlag)
760 | if err != nil {
761 | log.Fatal(err)
762 | }
763 | defer f.Close()
764 | o = f
765 | }
766 |
767 | if err := logic(o, &build.Default, posn, *noErrReturnStr); err != nil {
768 | if *formatFlag == "json" {
769 | jsonErr := json.NewEncoder(o).Encode(struct {
770 | Error string `json:"error"`
771 | }{
772 | Error: err.Error(),
773 | })
774 | if jsonErr != nil {
775 | log.Println(err)
776 | log.Fatal(jsonErr)
777 | }
778 | return
779 | }
780 | log.Fatal(err)
781 | }
782 | }
783 |
--------------------------------------------------------------------------------
/expanderr_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "flag"
7 | "go/build"
8 | "io/ioutil"
9 | "path/filepath"
10 | "strings"
11 | "testing"
12 | )
13 |
14 | func TestExpand(t *testing.T) {
15 | // Cannot be safely run in parallel as long as build.Default is overridden
16 | // t.Parallel()
17 |
18 | for _, entry := range []struct {
19 | name string
20 | fn string
21 | posn string
22 | errcallback string
23 | }{
24 | {"SingleErrorAfter", "testdata/singleerror.got/src/singleerror/singleerror.go", ":#90", ""},
25 | {"SingleErrorBefore", "testdata/singleerror.got/src/singleerror/singleerror.go", ":#69", ""},
26 | {"SingleErrorMiddle", "testdata/singleerror.got/src/singleerror/singleerror.go", ":#81", ""},
27 | {"NoReturn", "testdata/nocalleereturn.got/src/nocalleereturn/nocalleereturn.go", ":#75", ""},
28 | {"VariableAndError", "testdata/varanderror.got/src/varanderror/varanderror.go", ":#148", ""},
29 | {"Comment", "testdata/comment.got/src/comment/comment.go", ":#90", ""},
30 | {"CommentInline", "testdata/commentinline.got/src/commentinline/commentinline.go", ":#109", ""},
31 | {"NoReturnCaller", "testdata/noreturncaller.got/src/noreturncaller/noreturncaller.go", ":#77", ""},
32 | {"NoErrReturn", "testdata/noerrreturn.got/src/noerrreturn/noerrreturn.go", ":#81", ""},
33 | {"ReturnErrCall", "testdata/returnerrcall.got/src/returnerrcall/returnerrcall.go", ":#101", "log.Fatal(err.Error())"},
34 | {"FunctionLiteral", "testdata/functionliteral.got/src/functionliteral/functionliteral.go", ":#87", ""},
35 | // The following test spreads out one package over two files, exercising
36 | // the code path for loading multiple files.
37 | {"2Files1Pkg", "testdata/pkg.got/src/pkg/pkg2.go", ":#49", ""},
38 | // MultiPkg calls a function in another not-compiled, non-stdlib package.
39 | {"MultiPkg", "testdata/multipkg.got/src/multipkg/multipkg.go", ":#79", ""},
40 | {"MultiPkgVendor", "testdata/multipkgvendor.got/src/multipkg/multipkg.go", ":#79", ""},
41 | {"Underscore", "testdata/underscore.got/src/underscore/underscore.go", ":#162", ""},
42 | {"IntroduceErr", "testdata/introduceerr.got/src/introduceerr/introduceerr.go", ":#176", ""},
43 | {"NoIntroduce", "testdata/nointroduce.got/src/nointroduce/nointroduce.go", ":#165", ""},
44 | {"PresentSingle", "testdata/presentsingle.got/src/presentsingle/presentsingle.go", ":#90", ""},
45 | {"PresentDouble", "testdata/presentdouble.got/src/presentdouble/presentdouble.go", ":#105", ""},
46 | {"CustomTypes", "testdata/customtypes.got/src/customtypes/customtypes.go", ":#191", ""},
47 | } {
48 | entry := entry // copy
49 | t.Run(entry.name, func(t *testing.T) {
50 | // Cannot be safely run in parallel as long as build.Default is overridden
51 | // t.Parallel()
52 |
53 | flag.Set("format", "source")
54 |
55 | wantContents, err := ioutil.ReadFile(strings.Replace(entry.fn, ".got", ".want", 1))
56 | if err != nil {
57 | t.Fatal(err)
58 | }
59 |
60 | gopath, err := filepath.Abs(filepath.Join(strings.Split(entry.fn, "/")[:2]...))
61 | if err != nil {
62 | t.Fatal(err)
63 | }
64 | buildctx := build.Context{
65 | GOARCH: build.Default.GOARCH,
66 | GOOS: build.Default.GOOS,
67 | GOROOT: build.Default.GOROOT,
68 | GOPATH: gopath,
69 | Compiler: build.Default.Compiler,
70 | }
71 |
72 | var buf bytes.Buffer
73 | if err := logic(&buf, &buildctx, entry.fn+entry.posn, entry.errcallback); err != nil {
74 | t.Fatal(err)
75 | }
76 |
77 | if got, want := buf.String(), string(wantContents); got != want {
78 | t.Fatalf("unexpected result: have:\n%s\nwant:\n%s", got, want)
79 | }
80 |
81 | // Test the JSON output format as well.
82 |
83 | buf.Reset()
84 | flag.Set("format", "json")
85 |
86 | gotContents, err := ioutil.ReadFile(entry.fn)
87 | if err != nil {
88 | t.Fatal(err)
89 | }
90 |
91 | if err := logic(&buf, &buildctx, entry.fn+entry.posn, entry.errcallback); err != nil {
92 | t.Fatal(err)
93 | }
94 |
95 | var change struct {
96 | Start int `json:"start"`
97 | End int `json:"end"`
98 | Lines []string `json:"lines"`
99 | Warnings []string `json:"warnings"`
100 | }
101 | if err := json.Unmarshal(buf.Bytes(), &change); err != nil {
102 | t.Fatal(err)
103 | }
104 |
105 | lines := strings.Split(string(gotContents), "\n")
106 |
107 | replaced := make([]string, len(lines[:change.Start-1]))
108 | copy(replaced, lines)
109 | replaced = append(replaced, change.Lines...)
110 | replaced = append(replaced, lines[change.End:]...)
111 |
112 | if got, want := strings.Join(replaced, "\n"), string(wantContents); got != want {
113 | t.Fatalf("unexpected result: have:\n%s\nwant:\n%s", got, want)
114 | }
115 | })
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/internal/srcimporter/srcimporter.go:
--------------------------------------------------------------------------------
1 | // Copied from golang/go/src/go/internal/srcimporter
2 |
3 | // Copyright 2017 The Go Authors. All rights reserved.
4 | // Use of this source code is governed by a BSD-style
5 | // license that can be found in the LICENSE file.
6 |
7 | // Package srcimporter implements importing directly
8 | // from source files rather than installed packages.
9 | package srcimporter
10 |
11 | import (
12 | "fmt"
13 | "go/ast"
14 | "go/build"
15 | "go/parser"
16 | "go/token"
17 | "go/types"
18 | "path/filepath"
19 | "sync"
20 | )
21 |
22 | // An Importer provides the context for importing packages from source code.
23 | type Importer struct {
24 | ctxt *build.Context
25 | fset *token.FileSet
26 | packages map[string]*types.Package
27 | }
28 |
29 | // NewImporter returns a new Importer for the given context, file set, and map
30 | // of packages. The context is used to resolve import paths to package paths,
31 | // and identifying the files belonging to the package. If the context provides
32 | // non-nil file system functions, they are used instead of the regular package
33 | // os functions. The file set is used to track position information of package
34 | // files; and imported packages are added to the packages map.
35 | func New(ctxt *build.Context, fset *token.FileSet, packages map[string]*types.Package) *Importer {
36 | return &Importer{
37 | ctxt: ctxt,
38 | fset: fset,
39 | packages: packages,
40 | }
41 | }
42 |
43 | // Importing is a sentinel taking the place in Importer.packages
44 | // for a package that is in the process of being imported.
45 | var importing types.Package
46 |
47 | // Import(path) is a shortcut for ImportFrom(path, "", 0).
48 | func (p *Importer) Import(path string) (*types.Package, error) {
49 | return p.ImportFrom(path, "", 0)
50 | }
51 |
52 | // ImportFrom imports the package with the given import path resolved from the given srcDir,
53 | // adds the new package to the set of packages maintained by the importer, and returns the
54 | // package. Package path resolution and file system operations are controlled by the context
55 | // maintained with the importer. The import mode must be zero but is otherwise ignored.
56 | // Packages that are not comprised entirely of pure Go files may fail to import because the
57 | // type checker may not be able to determine all exported entities (e.g. due to cgo dependencies).
58 | func (p *Importer) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) {
59 | if mode != 0 {
60 | panic("non-zero import mode")
61 | }
62 |
63 | // determine package path (do vendor resolution)
64 | var bp *build.Package
65 | var err error
66 | switch {
67 | default:
68 | if abs, err := p.absPath(srcDir); err == nil { // see issue #14282
69 | srcDir = abs
70 | }
71 | bp, err = p.ctxt.Import(path, srcDir, build.FindOnly)
72 |
73 | case build.IsLocalImport(path):
74 | // "./x" -> "srcDir/x"
75 | bp, err = p.ctxt.ImportDir(filepath.Join(srcDir, path), build.FindOnly)
76 |
77 | case p.isAbsPath(path):
78 | return nil, fmt.Errorf("invalid absolute import path %q", path)
79 | }
80 | if err != nil {
81 | return nil, err // err may be *build.NoGoError - return as is
82 | }
83 |
84 | // package unsafe is known to the type checker
85 | if bp.ImportPath == "unsafe" {
86 | return types.Unsafe, nil
87 | }
88 |
89 | // no need to re-import if the package was imported completely before
90 | pkg := p.packages[bp.ImportPath]
91 | if pkg != nil {
92 | if pkg == &importing {
93 | return nil, fmt.Errorf("import cycle through package %q", bp.ImportPath)
94 | }
95 | if !pkg.Complete() {
96 | // Package exists but is not complete - we cannot handle this
97 | // at the moment since the source importer replaces the package
98 | // wholesale rather than augmenting it (see #19337 for details).
99 | // Return incomplete package with error (see #16088).
100 | return pkg, fmt.Errorf("reimported partially imported package %q", bp.ImportPath)
101 | }
102 | return pkg, nil
103 | }
104 |
105 | p.packages[bp.ImportPath] = &importing
106 | defer func() {
107 | // clean up in case of error
108 | // TODO(gri) Eventually we may want to leave a (possibly empty)
109 | // package in the map in all cases (and use that package to
110 | // identify cycles). See also issue 16088.
111 | if p.packages[bp.ImportPath] == &importing {
112 | p.packages[bp.ImportPath] = nil
113 | }
114 | }()
115 |
116 | // collect package files
117 | bp, err = p.ctxt.ImportDir(bp.Dir, 0)
118 | if err != nil {
119 | return nil, err // err may be *build.NoGoError - return as is
120 | }
121 | var filenames []string
122 | filenames = append(filenames, bp.GoFiles...)
123 | filenames = append(filenames, bp.CgoFiles...)
124 |
125 | files, err := p.parseFiles(bp.Dir, filenames)
126 | if err != nil {
127 | return nil, err
128 | }
129 |
130 | // type-check package files
131 | var firstHardErr error
132 | conf := types.Config{
133 | IgnoreFuncBodies: true,
134 | FakeImportC: true,
135 | // continue type-checking after the first error
136 | Error: func(err error) {
137 | if firstHardErr == nil && !err.(types.Error).Soft {
138 | firstHardErr = err
139 | }
140 | },
141 | Importer: p,
142 | }
143 | pkg, err = conf.Check(bp.ImportPath, p.fset, files, nil)
144 | if err != nil {
145 | // If there was a hard error it is possibly unsafe
146 | // to use the package as it may not be fully populated.
147 | // Do not return it (see also #20837, #20855).
148 | if firstHardErr != nil {
149 | pkg = nil
150 | err = firstHardErr // give preference to first hard error over any soft error
151 | }
152 | return pkg, fmt.Errorf("type-checking package %q failed (%v)", bp.ImportPath, err)
153 | }
154 | if firstHardErr != nil {
155 | // this can only happen if we have a bug in go/types
156 | panic("package is not safe yet no error was returned")
157 | }
158 |
159 | p.packages[bp.ImportPath] = pkg
160 | return pkg, nil
161 | }
162 |
163 | func (p *Importer) parseFiles(dir string, filenames []string) ([]*ast.File, error) {
164 | open := p.ctxt.OpenFile // possibly nil
165 |
166 | files := make([]*ast.File, len(filenames))
167 | errors := make([]error, len(filenames))
168 |
169 | var wg sync.WaitGroup
170 | wg.Add(len(filenames))
171 | for i, filename := range filenames {
172 | go func(i int, filepath string) {
173 | defer wg.Done()
174 | if open != nil {
175 | src, err := open(filepath)
176 | if err != nil {
177 | errors[i] = fmt.Errorf("opening package file %s failed (%v)", filepath, err)
178 | return
179 | }
180 | files[i], errors[i] = parser.ParseFile(p.fset, filepath, src, 0)
181 | src.Close() // ignore Close error - parsing may have succeeded which is all we need
182 | } else {
183 | // Special-case when ctxt doesn't provide a custom OpenFile and use the
184 | // parser's file reading mechanism directly. This appears to be quite a
185 | // bit faster than opening the file and providing an io.ReaderCloser in
186 | // both cases.
187 | // TODO(gri) investigate performance difference (issue #19281)
188 | files[i], errors[i] = parser.ParseFile(p.fset, filepath, nil, 0)
189 | }
190 | }(i, p.joinPath(dir, filename))
191 | }
192 | wg.Wait()
193 |
194 | // if there are errors, return the first one for deterministic results
195 | for _, err := range errors {
196 | if err != nil {
197 | return nil, err
198 | }
199 | }
200 |
201 | return files, nil
202 | }
203 |
204 | // context-controlled file system operations
205 |
206 | func (p *Importer) absPath(path string) (string, error) {
207 | // TODO(gri) This should be using p.ctxt.AbsPath which doesn't
208 | // exist but probably should. See also issue #14282.
209 | return filepath.Abs(path)
210 | }
211 |
212 | func (p *Importer) isAbsPath(path string) bool {
213 | if f := p.ctxt.IsAbsPath; f != nil {
214 | return f(path)
215 | }
216 | return filepath.IsAbs(path)
217 | }
218 |
219 | func (p *Importer) joinPath(elem ...string) string {
220 | if f := p.ctxt.JoinPath; f != nil {
221 | return f(elem...)
222 | }
223 | return filepath.Join(elem...)
224 | }
225 |
--------------------------------------------------------------------------------
/lisp/go-expanderr.el:
--------------------------------------------------------------------------------
1 | (require 'go-mode) ; we use a few gofmt-- functions.
2 |
3 | (defcustom go-expanderr-command "expanderr"
4 | "The `expanderr' command; by default, $PATH is searched."
5 | :type 'string
6 | :group 'expanderr)
7 |
8 | (defcustom go-expanderr-show-error-buffer t
9 | "Whether to show a the error buffer when `go-expanderr' failed."
10 | :type 'boolean
11 | :group 'expanderr)
12 |
13 | (defun go-expanderr ()
14 | "Expand the Call Expression before/under the cursor to check errors."
15 | (interactive)
16 | (let ((tmpfile (make-temp-file "expanderr" nil ".go"))
17 | (patchbuf (get-buffer-create "*Expanderr patch*"))
18 | (errbuf (if gofmt-show-errors (get-buffer-create "*Expanderr Errors*")))
19 | (coding-system-for-read 'utf-8)
20 | (coding-system-for-write 'utf-8)
21 | our-gofmt-args)
22 |
23 | (unwind-protect
24 | (save-restriction
25 | (widen)
26 | (if errbuf
27 | (with-current-buffer errbuf
28 | (setq buffer-read-only nil)
29 | (erase-buffer)))
30 | (with-current-buffer patchbuf
31 | (erase-buffer))
32 |
33 | (save-buffer)
34 | (setq expanderr-command go-expanderr-command)
35 | (setq our-expanderr-args (list "-w" tmpfile "-no-error-callback" "log.Fatal(err)"
36 | (concat
37 | (file-truename buffer-file-name)
38 | (format ":#%d" (position-bytes (point))))))
39 | (message "Calling expanderr: %s %s" expanderr-command our-expanderr-args)
40 | ;; We're using errbuf for the mixed stdout and stderr output. This
41 | ;; is not an issue because expanderr -w does not produce any stdout
42 | ;; output in case of success.
43 | (if (zerop (apply #'call-process expanderr-command nil errbuf nil our-expanderr-args))
44 | (progn
45 | (if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
46 | (message "Buffer is already expanded")
47 | (go--apply-rcs-patch patchbuf)
48 | (message "Applied expanderr"))
49 | (if errbuf (gofmt--kill-error-buffer errbuf)))
50 | (message "Could not apply expanderr")
51 | (when (and errbuf go-expanderr-show-error-buffer)
52 | (gofmt--process-errors (buffer-file-name) tmpfile errbuf))))
53 |
54 | (kill-buffer patchbuf)
55 | (delete-file tmpfile))))
56 |
57 | (define-key go-mode-map (kbd "C-c C-e") #'go-expanderr)
58 |
59 | (provide 'go-expanderr)
60 |
--------------------------------------------------------------------------------
/pos.go:
--------------------------------------------------------------------------------
1 | // Copyright 2013 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 | // This file defines utilities for working with file positions.
8 |
9 | import (
10 | "fmt"
11 | "go/token"
12 | "os"
13 | "path/filepath"
14 | "strconv"
15 | "strings"
16 | )
17 |
18 | // parseOctothorpDecimal returns the numeric value if s matches "#%d",
19 | // otherwise -1.
20 | func parseOctothorpDecimal(s string) int {
21 | if s != "" && s[0] == '#' {
22 | if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
23 | return int(s)
24 | }
25 | }
26 | return -1
27 | }
28 |
29 | // parsePos parses a string of the form "file:pos" or
30 | // file:start,end" where pos, start, end match #%d and represent byte
31 | // offsets, and returns its components.
32 | //
33 | // (Numbers without a '#' prefix are reserved for future use,
34 | // e.g. to indicate line/column positions.)
35 | //
36 | func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
37 | if pos == "" {
38 | err = fmt.Errorf("no source position specified")
39 | return
40 | }
41 |
42 | colon := strings.LastIndex(pos, ":")
43 | if colon < 0 {
44 | err = fmt.Errorf("bad position syntax %q", pos)
45 | return
46 | }
47 | filename, offset := pos[:colon], pos[colon+1:]
48 | startOffset = -1
49 | endOffset = -1
50 | if comma := strings.Index(offset, ","); comma < 0 {
51 | // e.g. "foo.go:#123"
52 | startOffset = parseOctothorpDecimal(offset)
53 | endOffset = startOffset
54 | } else {
55 | // e.g. "foo.go:#123,#456"
56 | startOffset = parseOctothorpDecimal(offset[:comma])
57 | endOffset = parseOctothorpDecimal(offset[comma+1:])
58 | }
59 | if startOffset < 1 || endOffset < startOffset {
60 | err = fmt.Errorf("invalid offset %q in query position", offset)
61 | return
62 | }
63 | return
64 | }
65 |
66 | // fileOffsetToPos translates the specified file-relative byte offsets
67 | // into token.Pos form. It returns an error if the file was not found
68 | // or the offsets were out of bounds.
69 | //
70 | func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
71 | // Range check [start..end], inclusive of both end-points.
72 |
73 | if 0 <= startOffset && startOffset <= file.Size() {
74 | start = file.Pos(int(startOffset))
75 | } else {
76 | err = fmt.Errorf("start position is beyond end of file")
77 | return
78 | }
79 |
80 | if 0 <= endOffset && endOffset <= file.Size() {
81 | end = file.Pos(int(endOffset))
82 | } else {
83 | err = fmt.Errorf("end position is beyond end of file")
84 | return
85 | }
86 |
87 | return
88 | }
89 |
90 | // sameFile returns true if x and y have the same basename and denote
91 | // the same file.
92 | //
93 | func sameFile(x, y string) bool {
94 | if filepath.Base(x) == filepath.Base(y) { // (optimisation)
95 | if xi, err := os.Stat(x); err == nil {
96 | if yi, err := os.Stat(y); err == nil {
97 | return os.SameFile(xi, yi)
98 | }
99 | }
100 | }
101 | return false
102 | }
103 |
--------------------------------------------------------------------------------
/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stapelberg/expanderr/792eb1fdcf7c23487ebef4034d4f122fadaec013/screencast.gif
--------------------------------------------------------------------------------
/screencast.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stapelberg/expanderr/792eb1fdcf7c23487ebef4034d4f122fadaec013/screencast.mp4
--------------------------------------------------------------------------------
/testdata/comment.got/src/comment/comment.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | os.Remove("/tmp/foo") // delete
10 | return 0, nil
11 | }
12 |
13 | func main() {
14 | if _, err := logic(); err != nil {
15 | log.Fatal(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/testdata/comment.want/src/comment/comment.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | if err := os.Remove("/tmp/foo"); err != nil {
10 | return 0, err
11 | } // delete
12 | return 0, nil
13 | }
14 |
15 | func main() {
16 | if _, err := logic(); err != nil {
17 | log.Fatal(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/testdata/commentinline.got/src/commentinline/commentinline.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | os.Remove("/tmp/foo" /*path*/) // delete
10 | return 0, nil
11 | }
12 |
13 | func main() {
14 | if _, err := logic(); err != nil {
15 | log.Fatal(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/testdata/commentinline.want/src/commentinline/commentinline.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | if err := os.Remove("/tmp/foo" /*path*/); err != nil {
10 | return 0, err
11 | } // delete
12 | return 0, nil
13 | }
14 |
15 | func main() {
16 | if _, err := logic(); err != nil {
17 | log.Fatal(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/testdata/customtypes.got/src/customtypes/customtypes.go:
--------------------------------------------------------------------------------
1 | package err
2 |
3 | import "os"
4 |
5 | type customstr string
6 |
7 | type customstruct struct{}
8 |
9 | type custominterface interface{}
10 |
11 | func err() (customstr, customstruct, custominterface, error) {
12 | a := os.Getwd()
13 | return nil
14 | }
15 |
--------------------------------------------------------------------------------
/testdata/customtypes.want/src/customtypes/customtypes.go:
--------------------------------------------------------------------------------
1 | package err
2 |
3 | import "os"
4 |
5 | type customstr string
6 |
7 | type customstruct struct{}
8 |
9 | type custominterface interface{}
10 |
11 | func err() (customstr, customstruct, custominterface, error) {
12 | a, err := os.Getwd()
13 | if err != nil {
14 | return "", customstruct{}, nil, err
15 | }
16 | return nil
17 | }
18 |
--------------------------------------------------------------------------------
/testdata/functionliteral.got/src/functionliteral/functionliteral.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | var boom func() error
8 |
9 | func testing() error {
10 | boom()
11 | return nil
12 | }
13 |
14 | func main() {
15 | if err := testing(); err != nil {
16 | log.Fatal(err.Error())
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/testdata/functionliteral.want/src/functionliteral/functionliteral.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | var boom func() error
8 |
9 | func testing() error {
10 | if err := boom(); err != nil {
11 | return err
12 | }
13 | return nil
14 | }
15 |
16 | func main() {
17 | if err := testing(); err != nil {
18 | log.Fatal(err.Error())
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/testdata/introduceerr.got/src/introduceerr/introduceerr.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | f, ferr := os.Create("/tmp/a")
10 | if ferr != nil {
11 | return 0, ferr
12 | }
13 | var n int
14 | n = f.Write([]byte("foo"))
15 | return 0, nil
16 | }
17 |
18 | func main() {
19 | if _, err := logic(); err != nil {
20 | log.Fatal(err)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/testdata/introduceerr.want/src/introduceerr/introduceerr.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | f, ferr := os.Create("/tmp/a")
10 | if ferr != nil {
11 | return 0, ferr
12 | }
13 | var n int
14 | var err error
15 | if n, err = f.Write([]byte("foo")); err != nil {
16 | return 0, err
17 | }
18 | return 0, nil
19 | }
20 |
21 | func main() {
22 | if _, err := logic(); err != nil {
23 | log.Fatal(err)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/testdata/multipkg.got/src/lib/lib.go:
--------------------------------------------------------------------------------
1 | package lib
2 |
3 | func Logic() (int, error) {
4 | return 23, nil
5 | }
6 |
--------------------------------------------------------------------------------
/testdata/multipkg.got/src/multipkg/multipkg.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "lib"
5 | "log"
6 | )
7 |
8 | func logic() error {
9 | i := lib.Logic()
10 | }
11 |
12 | func main() {
13 | if err := logic(); err != nil {
14 | log.Fatal(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/multipkg.want/src/multipkg/multipkg.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "lib"
5 | "log"
6 | )
7 |
8 | func logic() error {
9 | i, err := lib.Logic()
10 | if err != nil {
11 | return err
12 | }
13 | }
14 |
15 | func main() {
16 | if err := logic(); err != nil {
17 | log.Fatal(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/testdata/multipkgvendor.got/src/multipkg/multipkg.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "lib"
5 | "log"
6 | )
7 |
8 | func logic() error {
9 | i := lib.Logic()
10 | }
11 |
12 | func main() {
13 | if err := logic(); err != nil {
14 | log.Fatal(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/multipkgvendor.got/src/multipkg/vendor/lib/lib.go:
--------------------------------------------------------------------------------
1 | package lib
2 |
3 | func Logic() (int, error) {
4 | return 23, nil
5 | }
6 |
--------------------------------------------------------------------------------
/testdata/multipkgvendor.want/src/multipkg/multipkg.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "lib"
5 | "log"
6 | )
7 |
8 | func logic() error {
9 | i, err := lib.Logic()
10 | if err != nil {
11 | return err
12 | }
13 | }
14 |
15 | func main() {
16 | if err := logic(); err != nil {
17 | log.Fatal(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/testdata/nocalleereturn.got/src/nocalleereturn/nocalleereturn.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() error {
9 | os.Clearenv()
10 | }
11 |
12 | func main() {
13 | if err := logic(); err != nil {
14 | log.Fatal(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/nocalleereturn.want/src/nocalleereturn/nocalleereturn.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() error {
9 | os.Clearenv()
10 | }
11 |
12 | func main() {
13 | if err := logic(); err != nil {
14 | log.Fatal(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/noerrreturn.got/src/noerrreturn/noerrreturn.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() int {
9 | os.Remove("/tmp/foo")
10 | return 42
11 | }
12 |
13 | func main() {
14 | myInt := logic()
15 | log.Printf("ohai %d", myInt)
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/noerrreturn.want/src/noerrreturn/noerrreturn.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() int {
9 | if err := os.Remove("/tmp/foo"); err != nil {
10 | panic(err)
11 | }
12 | return 42
13 | }
14 |
15 | func main() {
16 | myInt := logic()
17 | log.Printf("ohai %d", myInt)
18 | }
19 |
--------------------------------------------------------------------------------
/testdata/nointroduce.got/src/nointroduce/nointroduce.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | f, ferr := os.Create("/tmp/a")
10 | if ferr != nil {
11 | return 0, ferr
12 | }
13 | _ = f.Write([]byte("foo"))
14 | return 0, nil
15 | }
16 |
17 | func main() {
18 | if _, err := logic(); err != nil {
19 | log.Fatal(err)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/testdata/nointroduce.want/src/nointroduce/nointroduce.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | f, ferr := os.Create("/tmp/a")
10 | if ferr != nil {
11 | return 0, ferr
12 | }
13 | if _, err := f.Write([]byte("foo")); err != nil {
14 | return 0, err
15 | }
16 | return 0, nil
17 | }
18 |
19 | func main() {
20 | if _, err := logic(); err != nil {
21 | log.Fatal(err)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/noreturncaller.got/src/noreturncaller/noreturncaller.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() {
9 | os.Remove("/tmp/foo")
10 | }
11 |
12 | func main() {
13 | log.Printf("ohai")
14 | logic()
15 | }
16 |
--------------------------------------------------------------------------------
/testdata/noreturncaller.want/src/noreturncaller/noreturncaller.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() {
9 | if err := os.Remove("/tmp/foo"); err != nil {
10 | panic(err)
11 | }
12 | }
13 |
14 | func main() {
15 | log.Printf("ohai")
16 | logic()
17 | }
18 |
--------------------------------------------------------------------------------
/testdata/pkg.got/src/pkg/pkg1.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | func logic() (string, error) {
4 | return "", nil
5 | }
6 |
--------------------------------------------------------------------------------
/testdata/pkg.got/src/pkg/pkg2.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | func caller() error {
4 | s := logic()
5 | }
6 |
--------------------------------------------------------------------------------
/testdata/pkg.want/src/pkg/pkg2.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | func caller() error {
4 | s, err := logic()
5 | if err != nil {
6 | return err
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/testdata/presentdouble.got/src/presentdouble/presentdouble.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | )
7 |
8 | func logic() (int, error) {
9 | n, err := ioutil.ReadAll(nil)
10 | return 0, nil
11 | }
12 |
13 | func main() {
14 | if _, err := logic(); err != nil {
15 | log.Fatal(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/testdata/presentdouble.want/src/presentdouble/presentdouble.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | )
7 |
8 | func logic() (int, error) {
9 | n, err := ioutil.ReadAll(nil)
10 | if err != nil {
11 | return 0, err
12 | }
13 | return 0, nil
14 | }
15 |
16 | func main() {
17 | if _, err := logic(); err != nil {
18 | log.Fatal(err)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/testdata/presentsingle.got/src/presentsingle/presentsingle.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | err := os.Remove("a")
10 | return 0, nil
11 | }
12 |
13 | func main() {
14 | if _, err := logic(); err != nil {
15 | log.Fatal(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/testdata/presentsingle.want/src/presentsingle/presentsingle.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | if err := os.Remove("a"); err != nil {
10 | return 0, err
11 | }
12 | return 0, nil
13 | }
14 |
15 | func main() {
16 | if _, err := logic(); err != nil {
17 | log.Fatal(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/testdata/returnerrcall.got/src/returnerrcall/returnerrcall.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | )
7 |
8 | func logic() (int, string) {
9 | b := ioutil.ReadAll(nil)
10 | return len(b), "hoi"
11 | }
12 |
13 | func main() {
14 | myInt, myStr := logic()
15 | fmt.Printf("%s, my number is %d", myStr, myInt)
16 | }
17 |
--------------------------------------------------------------------------------
/testdata/returnerrcall.want/src/returnerrcall/returnerrcall.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | )
7 |
8 | func logic() (int, string) {
9 | b, err := ioutil.ReadAll(nil)
10 | if err != nil {
11 | log.Fatal(err.Error())
12 | return 0, ""
13 | }
14 | return len(b), "hoi"
15 | }
16 |
17 | func main() {
18 | myInt, myStr := logic()
19 | fmt.Printf("%s, my number is %d", myStr, myInt)
20 | }
21 |
--------------------------------------------------------------------------------
/testdata/singleerror.got/src/singleerror/singleerror.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | os.Remove("/tmp/foo")
10 | return 0, nil
11 | }
12 |
13 | func main() {
14 | log.Printf("ohai")
15 | if _, err := logic(); err != nil {
16 | log.Fatal(err)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/testdata/singleerror.want/src/singleerror/singleerror.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | if err := os.Remove("/tmp/foo"); err != nil {
10 | return 0, err
11 | }
12 | return 0, nil
13 | }
14 |
15 | func main() {
16 | log.Printf("ohai")
17 | if _, err := logic(); err != nil {
18 | log.Fatal(err)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/testdata/underscore.got/src/underscore/underscore.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | f, err := os.Create("/tmp/a")
10 | if err != nil {
11 | return 0, err
12 | }
13 | _ = f.Write([]byte("foo"))
14 | return 0, nil
15 | }
16 |
17 | func main() {
18 | if _, err := logic(); err != nil {
19 | log.Fatal(err)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/testdata/underscore.want/src/underscore/underscore.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func logic() (int, error) {
9 | f, err := os.Create("/tmp/a")
10 | if err != nil {
11 | return 0, err
12 | }
13 | if _, err := f.Write([]byte("foo")); err != nil {
14 | return 0, err
15 | }
16 | return 0, nil
17 | }
18 |
19 | func main() {
20 | if _, err := logic(); err != nil {
21 | log.Fatal(err)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/testdata/varanderror.got/src/varanderror/varanderror.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "log"
7 | "strings"
8 | )
9 |
10 | func logic() (int, error) {
11 | n := io.Copy(ioutil.Discard, strings.NewReader("test"))
12 | return 0, nil
13 | }
14 |
15 | func main() {
16 | log.Printf("ohai")
17 | if _, err := logic(); err != nil {
18 | log.Fatal(err)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/testdata/varanderror.want/src/varanderror/varanderror.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "log"
7 | "strings"
8 | )
9 |
10 | func logic() (int, error) {
11 | n, err := io.Copy(ioutil.Discard, strings.NewReader("test"))
12 | if err != nil {
13 | return 0, err
14 | }
15 | return 0, nil
16 | }
17 |
18 | func main() {
19 | log.Printf("ohai")
20 | if _, err := logic(); err != nil {
21 | log.Fatal(err)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------