1// Copyright 2020 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 nonewvars defines an Analyzer that applies suggested fixes
6// to errors of the type "no new variables on left side of :=".
7package nonewvars
8
9import (
10	"bytes"
11	"go/ast"
12	"go/format"
13	"go/token"
14
15	"golang.org/x/tools/go/analysis"
16	"golang.org/x/tools/go/analysis/passes/inspect"
17	"golang.org/x/tools/go/ast/inspector"
18	"golang.org/x/tools/internal/analysisinternal"
19)
20
21const Doc = `suggested fixes for "no new vars on left side of :="
22
23This checker provides suggested fixes for type errors of the
24type "no new vars on left side of :=". For example:
25	z := 1
26	z := 2
27will turn into
28	z := 1
29	z = 2
30`
31
32var Analyzer = &analysis.Analyzer{
33	Name:             string(analysisinternal.NoNewVars),
34	Doc:              Doc,
35	Requires:         []*analysis.Analyzer{inspect.Analyzer},
36	Run:              run,
37	RunDespiteErrors: true,
38}
39
40func run(pass *analysis.Pass) (interface{}, error) {
41	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
42	errors := analysisinternal.GetTypeErrors(pass)
43
44	nodeFilter := []ast.Node{(*ast.AssignStmt)(nil)}
45	inspect.Preorder(nodeFilter, func(n ast.Node) {
46		assignStmt, _ := n.(*ast.AssignStmt)
47		// We only care about ":=".
48		if assignStmt.Tok != token.DEFINE {
49			return
50		}
51
52		var file *ast.File
53		for _, f := range pass.Files {
54			if f.Pos() <= assignStmt.Pos() && assignStmt.Pos() < f.End() {
55				file = f
56				break
57			}
58		}
59		if file == nil {
60			return
61		}
62
63		for _, err := range errors {
64			if !FixesError(err.Msg) {
65				continue
66			}
67			if assignStmt.Pos() > err.Pos || err.Pos >= assignStmt.End() {
68				continue
69			}
70			var buf bytes.Buffer
71			if err := format.Node(&buf, pass.Fset, file); err != nil {
72				continue
73			}
74			pass.Report(analysis.Diagnostic{
75				Pos:     err.Pos,
76				End:     analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos),
77				Message: err.Msg,
78				SuggestedFixes: []analysis.SuggestedFix{{
79					Message: "Change ':=' to '='",
80					TextEdits: []analysis.TextEdit{{
81						Pos: err.Pos,
82						End: err.Pos + 1,
83					}},
84				}},
85			})
86		}
87	})
88	return nil, nil
89}
90
91func FixesError(msg string) bool {
92	return msg == "no new variables on left side of :="
93}
94