github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/analysis/undeclaredname/undeclared.go (about)

     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 undeclaredname defines an Analyzer that applies suggested fixes
     6  // to errors of the type "undeclared name: %s".
     7  package undeclaredname
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/format"
    14  	"go/token"
    15  	"go/types"
    16  	"strings"
    17  	"unicode"
    18  
    19  	"github.com/powerman/golang-tools/go/analysis"
    20  	"github.com/powerman/golang-tools/go/ast/astutil"
    21  	"github.com/powerman/golang-tools/internal/analysisinternal"
    22  	"github.com/powerman/golang-tools/internal/span"
    23  )
    24  
    25  const Doc = `suggested fixes for "undeclared name: <>"
    26  
    27  This checker provides suggested fixes for type errors of the
    28  type "undeclared name: <>". It will either insert a new statement,
    29  such as:
    30  
    31  "<> := "
    32  
    33  or a new function declaration, such as:
    34  
    35  func <>(inferred parameters) {
    36  	panic("implement me!")
    37  }
    38  `
    39  
    40  var Analyzer = &analysis.Analyzer{
    41  	Name:             string(analysisinternal.UndeclaredName),
    42  	Doc:              Doc,
    43  	Requires:         []*analysis.Analyzer{},
    44  	Run:              run,
    45  	RunDespiteErrors: true,
    46  }
    47  
    48  const undeclaredNamePrefix = "undeclared name: "
    49  
    50  func run(pass *analysis.Pass) (interface{}, error) {
    51  	for _, err := range analysisinternal.GetTypeErrors(pass) {
    52  		runForError(pass, err)
    53  	}
    54  	return nil, nil
    55  }
    56  
    57  func runForError(pass *analysis.Pass, err types.Error) {
    58  	if !strings.HasPrefix(err.Msg, undeclaredNamePrefix) {
    59  		return
    60  	}
    61  	name := strings.TrimPrefix(err.Msg, undeclaredNamePrefix)
    62  	var file *ast.File
    63  	for _, f := range pass.Files {
    64  		if f.Pos() <= err.Pos && err.Pos < f.End() {
    65  			file = f
    66  			break
    67  		}
    68  	}
    69  	if file == nil {
    70  		return
    71  	}
    72  
    73  	// Get the path for the relevant range.
    74  	path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos)
    75  	if len(path) < 2 {
    76  		return
    77  	}
    78  	ident, ok := path[0].(*ast.Ident)
    79  	if !ok || ident.Name != name {
    80  		return
    81  	}
    82  
    83  	// Undeclared quick fixes only work in function bodies.
    84  	inFunc := false
    85  	for i := range path {
    86  		if _, inFunc = path[i].(*ast.FuncDecl); inFunc {
    87  			if i == 0 {
    88  				return
    89  			}
    90  			if _, isBody := path[i-1].(*ast.BlockStmt); !isBody {
    91  				return
    92  			}
    93  			break
    94  		}
    95  	}
    96  	if !inFunc {
    97  		return
    98  	}
    99  	// Skip selector expressions because it might be too complex
   100  	// to try and provide a suggested fix for fields and methods.
   101  	if _, ok := path[1].(*ast.SelectorExpr); ok {
   102  		return
   103  	}
   104  	tok := pass.Fset.File(file.Pos())
   105  	if tok == nil {
   106  		return
   107  	}
   108  	offset := pass.Fset.Position(err.Pos).Offset
   109  	end := tok.Pos(offset + len(name))
   110  	pass.Report(analysis.Diagnostic{
   111  		Pos:     err.Pos,
   112  		End:     end,
   113  		Message: err.Msg,
   114  	})
   115  }
   116  
   117  func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
   118  	pos := rng.Start // don't use the end
   119  	path, _ := astutil.PathEnclosingInterval(file, pos, pos)
   120  	if len(path) < 2 {
   121  		return nil, fmt.Errorf("no expression found")
   122  	}
   123  	ident, ok := path[0].(*ast.Ident)
   124  	if !ok {
   125  		return nil, fmt.Errorf("no identifier found")
   126  	}
   127  
   128  	// Check for a possible call expression, in which case we should add a
   129  	// new function declaration.
   130  	if len(path) > 1 {
   131  		if _, ok := path[1].(*ast.CallExpr); ok {
   132  			return newFunctionDeclaration(path, file, pkg, info, fset)
   133  		}
   134  	}
   135  
   136  	// Get the place to insert the new statement.
   137  	insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
   138  	if insertBeforeStmt == nil {
   139  		return nil, fmt.Errorf("could not locate insertion point")
   140  	}
   141  
   142  	insertBefore := fset.Position(insertBeforeStmt.Pos()).Offset
   143  
   144  	// Get the indent to add on the line after the new statement.
   145  	// Since this will have a parse error, we can not use format.Source().
   146  	contentBeforeStmt, indent := content[:insertBefore], "\n"
   147  	if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
   148  		indent = string(contentBeforeStmt[nl:])
   149  	}
   150  
   151  	// Create the new local variable statement.
   152  	newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
   153  	return &analysis.SuggestedFix{
   154  		Message: fmt.Sprintf("Create variable \"%s\"", ident.Name),
   155  		TextEdits: []analysis.TextEdit{{
   156  			Pos:     insertBeforeStmt.Pos(),
   157  			End:     insertBeforeStmt.Pos(),
   158  			NewText: []byte(newStmt),
   159  		}},
   160  	}, nil
   161  }
   162  
   163  func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*analysis.SuggestedFix, error) {
   164  	if len(path) < 3 {
   165  		return nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path)
   166  	}
   167  	ident, ok := path[0].(*ast.Ident)
   168  	if !ok {
   169  		return nil, fmt.Errorf("no name for function declaration %v (%T)", path[0], path[0])
   170  	}
   171  	call, ok := path[1].(*ast.CallExpr)
   172  	if !ok {
   173  		return nil, fmt.Errorf("no call expression found %v (%T)", path[1], path[1])
   174  	}
   175  
   176  	// Find the enclosing function, so that we can add the new declaration
   177  	// below.
   178  	var enclosing *ast.FuncDecl
   179  	for _, n := range path {
   180  		if n, ok := n.(*ast.FuncDecl); ok {
   181  			enclosing = n
   182  			break
   183  		}
   184  	}
   185  	// TODO(rstambler): Support the situation when there is no enclosing
   186  	// function.
   187  	if enclosing == nil {
   188  		return nil, fmt.Errorf("no enclosing function found: %v", path)
   189  	}
   190  
   191  	pos := enclosing.End()
   192  
   193  	var paramNames []string
   194  	var paramTypes []types.Type
   195  	// keep track of all param names to later ensure uniqueness
   196  	nameCounts := map[string]int{}
   197  	for _, arg := range call.Args {
   198  		typ := info.TypeOf(arg)
   199  		if typ == nil {
   200  			return nil, fmt.Errorf("unable to determine type for %s", arg)
   201  		}
   202  
   203  		switch t := typ.(type) {
   204  		// this is the case where another function call returning multiple
   205  		// results is used as an argument
   206  		case *types.Tuple:
   207  			n := t.Len()
   208  			for i := 0; i < n; i++ {
   209  				name := typeToArgName(t.At(i).Type())
   210  				nameCounts[name]++
   211  
   212  				paramNames = append(paramNames, name)
   213  				paramTypes = append(paramTypes, types.Default(t.At(i).Type()))
   214  			}
   215  
   216  		default:
   217  			// does the argument have a name we can reuse?
   218  			// only happens in case of a *ast.Ident
   219  			var name string
   220  			if ident, ok := arg.(*ast.Ident); ok {
   221  				name = ident.Name
   222  			}
   223  
   224  			if name == "" {
   225  				name = typeToArgName(typ)
   226  			}
   227  
   228  			nameCounts[name]++
   229  
   230  			paramNames = append(paramNames, name)
   231  			paramTypes = append(paramTypes, types.Default(typ))
   232  		}
   233  	}
   234  
   235  	for n, c := range nameCounts {
   236  		// Any names we saw more than once will need a unique suffix added
   237  		// on. Reset the count to 1 to act as the suffix for the first
   238  		// occurrence of that name.
   239  		if c >= 2 {
   240  			nameCounts[n] = 1
   241  		} else {
   242  			delete(nameCounts, n)
   243  		}
   244  	}
   245  
   246  	params := &ast.FieldList{}
   247  
   248  	for i, name := range paramNames {
   249  		if suffix, repeats := nameCounts[name]; repeats {
   250  			nameCounts[name]++
   251  			name = fmt.Sprintf("%s%d", name, suffix)
   252  		}
   253  
   254  		// only worth checking after previous param in the list
   255  		if i > 0 {
   256  			// if type of parameter at hand is the same as the previous one,
   257  			// add it to the previous param list of identifiers so to have:
   258  			//  (s1, s2 string)
   259  			// and not
   260  			//  (s1 string, s2 string)
   261  			if paramTypes[i] == paramTypes[i-1] {
   262  				params.List[len(params.List)-1].Names = append(params.List[len(params.List)-1].Names, ast.NewIdent(name))
   263  				continue
   264  			}
   265  		}
   266  
   267  		params.List = append(params.List, &ast.Field{
   268  			Names: []*ast.Ident{
   269  				ast.NewIdent(name),
   270  			},
   271  			Type: analysisinternal.TypeExpr(fset, file, pkg, paramTypes[i]),
   272  		})
   273  	}
   274  
   275  	decl := &ast.FuncDecl{
   276  		Name: ast.NewIdent(ident.Name),
   277  		Type: &ast.FuncType{
   278  			Params: params,
   279  			// TODO(rstambler): Also handle result parameters here.
   280  		},
   281  		Body: &ast.BlockStmt{
   282  			List: []ast.Stmt{
   283  				&ast.ExprStmt{
   284  					X: &ast.CallExpr{
   285  						Fun: ast.NewIdent("panic"),
   286  						Args: []ast.Expr{
   287  							&ast.BasicLit{
   288  								Value: `"unimplemented"`,
   289  							},
   290  						},
   291  					},
   292  				},
   293  			},
   294  		},
   295  	}
   296  
   297  	b := bytes.NewBufferString("\n\n")
   298  	if err := format.Node(b, fset, decl); err != nil {
   299  		return nil, err
   300  	}
   301  	return &analysis.SuggestedFix{
   302  		Message: fmt.Sprintf("Create function \"%s\"", ident.Name),
   303  		TextEdits: []analysis.TextEdit{{
   304  			Pos:     pos,
   305  			End:     pos,
   306  			NewText: b.Bytes(),
   307  		}},
   308  	}, nil
   309  }
   310  func typeToArgName(ty types.Type) string {
   311  	s := types.Default(ty).String()
   312  
   313  	switch t := ty.(type) {
   314  	case *types.Basic:
   315  		// use first letter in type name for basic types
   316  		return s[0:1]
   317  	case *types.Slice:
   318  		// use element type to decide var name for slices
   319  		return typeToArgName(t.Elem())
   320  	case *types.Array:
   321  		// use element type to decide var name for arrays
   322  		return typeToArgName(t.Elem())
   323  	case *types.Chan:
   324  		return "ch"
   325  	}
   326  
   327  	s = strings.TrimFunc(s, func(r rune) bool {
   328  		return !unicode.IsLetter(r)
   329  	})
   330  
   331  	if s == "error" {
   332  		return "err"
   333  	}
   334  
   335  	// remove package (if present)
   336  	// and make first letter lowercase
   337  	a := []rune(s[strings.LastIndexByte(s, '.')+1:])
   338  	a[0] = unicode.ToLower(a[0])
   339  	return string(a)
   340  }