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.
5 // Package undeclaredname defines an Analyzer that applies suggested fixes
6 // to errors of the type "undeclared name: %s".
17 "golang.org/x/tools/go/analysis"
18 "golang.org/x/tools/go/ast/astutil"
19 "golang.org/x/tools/internal/analysisinternal"
20 "golang.org/x/tools/internal/span"
23 const Doc = `suggested fixes for "undeclared name: <>"
25 This checker provides suggested fixes for type errors of the
26 type "undeclared name: <>". It will insert a new statement:
29 var Analyzer = &analysis.Analyzer{
30 Name: string(analysisinternal.UndeclaredName),
32 Requires: []*analysis.Analyzer{},
34 RunDespiteErrors: true,
37 const undeclaredNamePrefix = "undeclared name: "
39 func run(pass *analysis.Pass) (interface{}, error) {
40 for _, err := range analysisinternal.GetTypeErrors(pass) {
41 if !FixesError(err.Msg) {
44 name := strings.TrimPrefix(err.Msg, undeclaredNamePrefix)
46 for _, f := range pass.Files {
47 if f.Pos() <= err.Pos && err.Pos < f.End() {
56 // Get the path for the relevant range.
57 path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos)
61 ident, ok := path[0].(*ast.Ident)
62 if !ok || ident.Name != name {
65 // Skip selector expressions because it might be too complex
66 // to try and provide a suggested fix for fields and methods.
67 if _, ok := path[1].(*ast.SelectorExpr); ok {
70 // TODO(golang.org/issue/34644): Handle call expressions with suggested
71 // fixes to create a function.
72 if _, ok := path[1].(*ast.CallExpr); ok {
75 tok := pass.Fset.File(file.Pos())
79 offset := pass.Fset.Position(err.Pos).Offset
80 end := tok.Pos(offset + len(name))
81 pass.Report(analysis.Diagnostic{
90 func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, _ *types.Package, _ *types.Info) (*analysis.SuggestedFix, error) {
91 pos := rng.Start // don't use the end
92 path, _ := astutil.PathEnclosingInterval(file, pos, pos)
94 return nil, fmt.Errorf("")
96 ident, ok := path[0].(*ast.Ident)
98 return nil, fmt.Errorf("")
100 // Get the place to insert the new statement.
101 insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
102 if insertBeforeStmt == nil {
103 return nil, fmt.Errorf("")
106 insertBefore := fset.Position(insertBeforeStmt.Pos()).Offset
108 // Get the indent to add on the line after the new statement.
109 // Since this will have a parse error, we can not use format.Source().
110 contentBeforeStmt, indent := content[:insertBefore], "\n"
111 if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
112 indent = string(contentBeforeStmt[nl:])
114 // Create the new local variable statement.
115 newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
116 return &analysis.SuggestedFix{
117 Message: fmt.Sprintf("Create variable \"%s\"", ident.Name),
118 TextEdits: []analysis.TextEdit{{
119 Pos: insertBeforeStmt.Pos(),
120 End: insertBeforeStmt.Pos(),
121 NewText: []byte(newStmt),
126 func FixesError(msg string) bool {
127 return strings.HasPrefix(msg, undeclaredNamePrefix)