// 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 source import ( "context" "fmt" "go/ast" "go/token" "go/types" "strings" "golang.org/x/tools/go/ast/astutil" ) type SignatureInformation struct { Label string Parameters []ParameterInformation ActiveParameter int } type ParameterInformation struct { Label string } func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInformation, error) { fAST := f.GetAST(ctx) pkg := f.GetPackage(ctx) if pkg == nil || pkg.IsIllTyped() { return nil, fmt.Errorf("package for %s is ill typed", f.URI()) } // Find a call expression surrounding the query position. var callExpr *ast.CallExpr path, _ := astutil.PathEnclosingInterval(fAST, pos, pos) if path == nil { return nil, fmt.Errorf("cannot find node enclosing position") } for _, node := range path { if c, ok := node.(*ast.CallExpr); ok && pos >= c.Lparen && pos <= c.Rparen { callExpr = c break } } if callExpr == nil || callExpr.Fun == nil { return nil, fmt.Errorf("cannot find an enclosing function") } // Get the type information for the function being called. sigType := pkg.GetTypesInfo().TypeOf(callExpr.Fun) if sigType == nil { return nil, fmt.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun) } sig, _ := sigType.Underlying().(*types.Signature) if sig == nil { return nil, fmt.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun) } qf := qualifier(fAST, pkg.GetTypes(), pkg.GetTypesInfo()) var paramInfo []ParameterInformation for i := 0; i < sig.Params().Len(); i++ { param := sig.Params().At(i) label := types.TypeString(param.Type(), qf) if sig.Variadic() && i == sig.Params().Len()-1 { label = strings.Replace(label, "[]", "...", 1) } if param.Name() != "" { label = fmt.Sprintf("%s %s", param.Name(), label) } paramInfo = append(paramInfo, ParameterInformation{ Label: label, }) } // Determine the query position relative to the number of parameters in the function. var activeParam int var start, end token.Pos for _, expr := range callExpr.Args { if start == token.NoPos { start = expr.Pos() } end = expr.End() if start <= pos && pos <= end { break } // Don't advance the active parameter for the last parameter of a variadic function. if !sig.Variadic() || activeParam < sig.Params().Len()-1 { activeParam++ } start = expr.Pos() + 1 // to account for commas } // Get the object representing the function, if available. // There is no object in certain cases such as calling a function returned by // a function (e.g. "foo()()"). var obj types.Object switch t := callExpr.Fun.(type) { case *ast.Ident: obj = pkg.GetTypesInfo().ObjectOf(t) case *ast.SelectorExpr: obj = pkg.GetTypesInfo().ObjectOf(t.Sel) } var name string if obj != nil { name = obj.Name() } else { name = "func" } results, writeResultParens := formatResults(sig.Results(), qf) label, detail := formatFunction(name, formatParams(sig.Params(), sig.Variadic(), qf), results, writeResultParens) if sig.Results().Len() > 0 { label += " " + detail } return &SignatureInformation{ Label: label, Parameters: paramInfo, ActiveParameter: activeParam, }, nil }