1
0
mirror of https://github.com/golang/go synced 2024-11-18 07:04:52 -07:00

internal/lsp: add ast fields to comment completion for declarations

* adds support for comment completion inside declarations
* improves scoring for completion results for comments
* adds comment completion support for non-exported symbols
* adds pruning for results that don't match text surrounding cursor
* tests for comment completion

Change-Id: Icb445a469cee3122fe032630bee037c7bdfe2e18
Reviewed-on: https://go-review.googlesource.com/c/tools/+/249639
Run-TryBot: Danish Dua <danishdua@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Danish Dua 2020-08-19 18:48:34 -04:00
parent daa6538899
commit 3509cdc6e9
5 changed files with 195 additions and 77 deletions

View File

@ -559,6 +559,9 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, protoPos
// If we're inside a comment return comment completions
for _, comment := range pgf.File.Comments {
if comment.Pos() < rng.Start && rng.Start <= comment.End() {
// deep completion doesn't work properly in comments since we don't
// have a type object to complete further
c.deepState.maxDepth = 0
c.populateCommentCompletions(ctx, comment)
return c.items, c.getSurrounding(), nil
}
@ -721,8 +724,7 @@ func (c *completer) emptySwitchStmt() bool {
}
}
// populateCommentCompletions yields completions for exported
// symbols immediately preceding comment.
// populateCommentCompletions yields completions for comments preceding or in declarationss
func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast.CommentGroup) {
// Using the comment position find the line after
file := c.snapshot.FileSet().File(comment.End())
@ -730,22 +732,18 @@ func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast
return
}
line := file.Line(comment.End())
if file.LineCount() < line+1 {
return
}
nextLinePos := file.LineStart(line + 1)
if !nextLinePos.IsValid() {
return
}
commentLine := file.Line(comment.End())
// comment is valid, set surrounding as word boundaries around cursor
c.setSurroundingForComment(comment)
cursorText := c.surrounding.content
// Using the next line pos, grab and parse the exported symbol on that line
for _, n := range c.file.Decls {
if n.Pos() != nextLinePos {
declLine := file.Line(n.Pos())
// if the comment is not in, directly above or on the same line as a declaration
if declLine != commentLine && declLine != commentLine+1 &&
!(n.Pos() <= comment.Pos() && comment.End() <= n.End()) {
continue
}
switch node := n.(type) {
@ -755,23 +753,85 @@ func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast
switch spec := spec.(type) {
case *ast.ValueSpec:
for _, name := range spec.Names {
if name.String() == "_" || !name.IsExported() {
if name.String() == "_" || !strings.HasPrefix(name.String(), cursorText) {
continue
}
obj := c.pkg.GetTypesInfo().ObjectOf(name)
c.found(ctx, candidate{obj: obj, score: stdScore})
}
case *ast.TypeSpec:
if spec.Name.String() == "_" || !spec.Name.IsExported() {
// add TypeSpec fields to completion
switch typeNode := spec.Type.(type) {
case *ast.StructType:
c.addFieldItems(ctx, typeNode.Fields)
case *ast.FuncType:
c.addFieldItems(ctx, typeNode.Params)
c.addFieldItems(ctx, typeNode.Results)
case *ast.InterfaceType:
c.addFieldItems(ctx, typeNode.Methods)
}
if spec.Name.String() == "_" || !strings.HasPrefix(spec.Name.String(), cursorText) {
continue
}
obj := c.pkg.GetTypesInfo().ObjectOf(spec.Name)
c.found(ctx, candidate{obj: obj, score: stdScore})
// Type name should get a higher score than fields but not highScore by default
// since field near a comment cursor gets a highScore
score := stdScore * 1.1
// If type declaration is on the line after comment, give it a highScore.
if declLine == commentLine+1 {
score = highScore
}
// we use c.item in addFieldItems so we have to use c.item here to ensure scoring
// order is maintained. c.found manipulates the score
if item, err := c.item(ctx, candidate{obj: obj, name: obj.Name(), score: score}); err == nil {
c.items = append(c.items, item)
}
}
}
// handle functions
case *ast.FuncDecl:
if node.Name.String() == "_" || !node.Name.IsExported() {
c.addFieldItems(ctx, node.Recv)
c.addFieldItems(ctx, node.Type.Params)
c.addFieldItems(ctx, node.Type.Results)
// collect receiver struct fields
if node.Recv != nil {
for _, fields := range node.Recv.List {
for _, name := range fields.Names {
obj := c.pkg.GetTypesInfo().ObjectOf(name)
if obj == nil {
continue
}
recvType := obj.Type().Underlying()
if ptr, ok := recvType.(*types.Pointer); ok {
recvType = ptr.Elem()
}
recvStruct, ok := recvType.Underlying().(*types.Struct)
if !ok {
continue
}
for i := 0; i < recvStruct.NumFields(); i++ {
field := recvStruct.Field(i)
if !strings.HasPrefix(field.Name(), cursorText) {
continue
}
// we use c.item in addFieldItems so we have to use c.item here to ensure scoring
// order is maintained. c.found maniplulates the score
item, err := c.item(ctx, candidate{obj: field, name: field.Name(), score: lowScore})
if err != nil {
continue
}
c.items = append(c.items, item)
}
}
}
}
if node.Name.String() == "_" || !strings.HasPrefix(node.Name.String(), cursorText) {
continue
}
@ -780,13 +840,13 @@ func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast
continue
}
// We don't want expandFuncCall inside comments. We add this directly to the
// completions list because using c.found sets expandFuncCall to true by default
// We don't want to expandFuncCall inside comments.
// c.found() doesn't respect this setting
item, err := c.item(ctx, candidate{
obj: obj,
name: obj.Name(),
expandFuncCall: false,
score: stdScore,
score: highScore,
})
if err != nil {
continue
@ -835,6 +895,45 @@ func isValidIdentifierChar(char byte) bool {
return unicode.In(charRune, unicode.Letter, unicode.Digit) || char == '_'
}
// adds struct fields, interface methods, function declaration fields to completion
func (c *completer) addFieldItems(ctx context.Context, fields *ast.FieldList) {
if fields == nil {
return
}
cursor := c.surrounding.cursor
surroundingPrefix := c.surrounding.content
for _, field := range fields.List {
for _, name := range field.Names {
if name.String() == "_" ||
!strings.HasPrefix(name.String(), surroundingPrefix) {
continue
}
obj := c.pkg.GetTypesInfo().ObjectOf(name)
// if we're in a field comment/doc, score that field as more relevant
score := stdScore
if field.Comment != nil && field.Comment.Pos() <= cursor && cursor <= field.Comment.End() {
score = highScore
} else if field.Doc != nil && field.Doc.Pos() <= cursor && cursor <= field.Doc.End() {
score = highScore
}
cand := candidate{
obj: obj,
name: obj.Name(),
expandFuncCall: false,
score: score,
}
// We don't want to expandFuncCall inside comments.
// c.found() doesn't respect this setting
if item, err := c.item(ctx, cand); err == nil {
c.items = append(c.items, item)
}
}
}
}
func (c *completer) wantStructFieldCompletions() bool {
clInfo := c.enclosingCompositeLiteral
if clInfo == nil {

View File

@ -0,0 +1,70 @@
package comment_completion
var p bool
//@complete(re"$")
func _() {
var a int
switch a {
case 1:
//@complete(re"$")
_ = a
}
var b chan int
select {
case <-b:
//@complete(re"$")
_ = b
}
var (
//@complete(re"$")
_ = a
)
}
// //@complete(" ", variableC)
var C string //@item(variableC, "C", "string", "var") //@complete(" ", variableC)
// //@complete(" ", constant)
const Constant = "example" //@item(constant, "Constant", "string", "const") //@complete(" ", constant)
// //@complete(" ", structType, fieldA, fieldB)
type StructType struct { //@item(structType, "StructType", "struct{...}", "struct") //@complete(" ", structType, fieldA, fieldB)
// //@complete(" ", fieldA, structType, fieldB)
A string //@item(fieldA, "A", "string", "field") //@complete(" ", fieldA, structType, fieldB)
b int //@item(fieldB, "b", "int", "field") //@complete(" ", fieldB, structType, fieldA)
}
// //@complete(" ", method, paramX, resultY, structRecv, fieldA, fieldB)
func (structType *StructType) Method(X int) (Y int) { //@item(structRecv, "structType", "*StructType", "var"),item(method, "Method", "func(X int) (Y int)", "method"),item(paramX, "X", "int", "var"),item(resultY, "Y", "int", "var")
// //@complete(" ", method, paramX, resultY, structRecv, fieldA, fieldB)
return
}
// //@complete(" ", newType)
type NewType string //@item(newType, "NewType", "string", "type") //@complete(" ", newType)
// //@complete(" ", testInterface, testA, testB)
type TestInterface interface { //@item(testInterface, "TestInterface", "interface{...}", "interface")
// //@complete(" ", testA, testInterface, testB)
TestA(L string) (M int) //@item(testA, "TestA", "func(L string) (M int)", "method"),item(paramL, "L", "var", "string"),item(resM, "M", "var", "int") //@complete(" ", testA, testInterface, testB)
TestB(N int) bool //@item(testB, "TestB", "func(N int) bool", "method"),item(paramN, "N", "var", "int") //@complete(" ", testB, testInterface, testA)
}
// //@complete(" ", function)
func Function() int { //@item(function, "Function", "func() int", "func") //@complete(" ", function)
// //@complete(" ", function)
return 0
}
// This tests multiline block comments and completion with prefix
// Lorem Ipsum Multili//@complete("Multi", multiline)
// Lorem ipsum dolor sit ametom
func Multiline() int { //@item(multiline, "Multiline", "func() int", "func")
// //@complete(" ", multiline)
return 0
}

View File

@ -1,27 +0,0 @@
package comments
var p bool
//@complete(re"$")
func _() {
var a int
switch a {
case 1:
//@complete(re"$")
_ = a
}
var b chan int
select {
case <-b:
//@complete(re"$")
_ = b
}
var (
//@complete(re"$")
_ = a
)
}

View File

@ -1,29 +1,5 @@
package complit
// exported comment completions
// //@complete(" ", cVar)
var C string //@item(cVar, "C", "string", "var")
// //@complete(" ", exportedConst)
const ExportedConst = "example" //@item(exportedConst, "ExportedConst", "string", "const")
// //@complete(" ", exportedType)
type ExportedType struct { //@item(exportedType, "ExportedType", "struct{...}", "struct")
}
// //@complete(" ", exportedFunc)
func ExportedFunc() int { //@item(exportedFunc, "ExportedFunc", "func() int", "func")
return 0
}
// This tests multiline block comments and completion with prefix
// Lorem Ipsum Multi//@complete(" ", multilineWithPrefix)
// Lorem ipsum dolor sit ametom
func MultilineWithPrefix() int { //@item(multilineWithPrefix, "MultilineWithPrefix", "func() int", "func")
return 0
}
// general completions
type position struct { //@item(structPosition, "position", "struct{...}", "struct")
@ -32,7 +8,7 @@ type position struct { //@item(structPosition, "position", "struct{...}", "struc
func _() {
_ = position{
//@complete("", fieldX, fieldY, exportedFunc, multilineWithPrefix, structPosition, cVar, exportedConst, exportedType)
//@complete("", fieldX, fieldY, structPosition)
}
_ = position{
X: 1,
@ -44,7 +20,7 @@ func _() {
}
_ = []*position{
{
//@complete("", fieldX, fieldY, exportedFunc, multilineWithPrefix, structPosition, cVar, exportedConst, exportedType)
//@complete("", fieldX, fieldY, structPosition)
},
}
}
@ -60,7 +36,7 @@ func _() {
}
_ = map[int]int{
//@complete("", abVar, exportedFunc, multilineWithPrefix, aaVar, structPosition, cVar, exportedConst, exportedType)
//@complete("", abVar, aaVar, structPosition)
}
_ = []string{a: ""} //@complete(":", abVar, aaVar)
@ -68,7 +44,7 @@ func _() {
_ = position{X: a} //@complete("}", abVar, aaVar)
_ = position{a} //@complete("}", abVar, aaVar)
_ = position{a, } //@complete("}", abVar, exportedFunc, multilineWithPrefix, aaVar, structPosition, cVar, exportedConst, exportedType)
_ = position{a, } //@complete("}", abVar, aaVar, structPosition)
_ = []int{a} //@complete("}", abVar, aaVar)
_ = [1]int{a} //@complete("}", abVar, aaVar)
@ -110,7 +86,7 @@ func _() {
func _() {
_ := position{
X: 1, //@complete("X", fieldX),complete(" 1", exportedFunc, multilineWithPrefix, structPosition, cVar, exportedConst, exportedType)
Y: , //@complete(":", fieldY),complete(" ,", exportedFunc, multilineWithPrefix, structPosition, cVar, exportedConst, exportedType)
X: 1, //@complete("X", fieldX),complete(" 1", structPosition)
Y: , //@complete(":", fieldY),complete(" ,", structPosition)
}
}

View File

@ -1,7 +1,7 @@
-- summary --
CallHierarchyCount = 1
CodeLensCount = 5
CompletionsCount = 239
CompletionsCount = 247
CompletionSnippetCount = 85
UnimportedCompletionsCount = 6
DeepCompletionsCount = 5