gopls/internal/golang: add semantic tokens for control labels

Fixes golang/go#65494

Change-Id: Id044cd12a5c48bb3d5dc8f91657febdc431811b4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/562244
Reviewed-by: Peter Weinberger <pjw@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Alan Donovan 2024-02-07 10:25:56 -05:00
Родитель 0d171942e7
Коммит 32d313923a
5 изменённых файлов: 44 добавлений и 6 удалений

Просмотреть файл

@ -231,7 +231,9 @@ func (tv *tokenVisitor) inspect(n ast.Node) (descend bool) {
case *ast.BlockStmt: case *ast.BlockStmt:
case *ast.BranchStmt: case *ast.BranchStmt:
tv.token(n.TokPos, len(n.Tok.String()), semtok.TokKeyword, nil) tv.token(n.TokPos, len(n.Tok.String()), semtok.TokKeyword, nil)
// There's no semantic encoding for labels if n.Label != nil {
tv.token(n.Label.Pos(), len(n.Label.Name), semtok.TokLabel, nil)
}
case *ast.CallExpr: case *ast.CallExpr:
if n.Ellipsis.IsValid() { if n.Ellipsis.IsValid() {
tv.token(n.Ellipsis, len("..."), semtok.TokOperator, nil) tv.token(n.Ellipsis, len("..."), semtok.TokOperator, nil)
@ -303,6 +305,7 @@ func (tv *tokenVisitor) inspect(n ast.Node) (descend bool) {
tv.token(n.Interface, len("interface"), semtok.TokKeyword, nil) tv.token(n.Interface, len("interface"), semtok.TokKeyword, nil)
case *ast.KeyValueExpr: case *ast.KeyValueExpr:
case *ast.LabeledStmt: case *ast.LabeledStmt:
tv.token(n.Label.Pos(), len(n.Label.Name), semtok.TokLabel, []string{"definition"})
case *ast.MapType: case *ast.MapType:
tv.token(n.Map, len("map"), semtok.TokKeyword, nil) tv.token(n.Map, len("map"), semtok.TokKeyword, nil)
case *ast.ParenExpr: case *ast.ParenExpr:
@ -411,7 +414,7 @@ func (tv *tokenVisitor) ident(id *ast.Ident) {
case *types.Func: case *types.Func:
emit(semtok.TokFunction) emit(semtok.TokFunction)
case *types.Label: case *types.Label:
// nothing to map it to // Labels are reliably covered by the syntax traversal.
case *types.Nil: case *types.Nil:
// nil is a predeclared identifier // nil is a predeclared identifier
emit(semtok.TokVariable, "readonly", "defaultLibrary") emit(semtok.TokVariable, "readonly", "defaultLibrary")
@ -571,8 +574,14 @@ func (tv *tokenVisitor) unkIdent(id *ast.Ident) (semtok.TokenType, []string) {
return semtok.TokMethod, def return semtok.TokMethod, def
} }
return semtok.TokVariable, nil return semtok.TokVariable, nil
case *ast.LabeledStmt, *ast.BranchStmt: case *ast.LabeledStmt:
// nothing to report if id == parent.Label {
return semtok.TokLabel, def
}
case *ast.BranchStmt:
if id == parent.Label {
return semtok.TokLabel, nil
}
case *ast.CompositeLit: case *ast.CompositeLit:
if parent.Type == id { if parent.Type == id {
return semtok.TokType, nil return semtok.TokType, nil
@ -604,7 +613,17 @@ func isDeprecated(n *ast.CommentGroup) bool {
// definitionFor handles a defining identifier. // definitionFor handles a defining identifier.
func (tv *tokenVisitor) definitionFor(id *ast.Ident, obj types.Object) (semtok.TokenType, []string) { func (tv *tokenVisitor) definitionFor(id *ast.Ident, obj types.Object) (semtok.TokenType, []string) {
// PJW: obj == types.Label? probably a nothing // The definition of a types.Label cannot be found by
// ascending the syntax tree, and doing so will reach the
// FuncDecl, causing us to misinterpret the label as a
// parameter (#65494).
//
// However, labels are reliably covered by the syntax
// traversal, so we don't need to use type information.
if is[*types.Label](obj) {
return "", nil
}
// PJW: look into replacing these syntactic tests with types more generally // PJW: look into replacing these syntactic tests with types more generally
modifiers := []string{"definition"} modifiers := []string{"definition"}
for i := len(tv.stack) - 1; i >= 0; i-- { for i := len(tv.stack) - 1; i >= 0; i-- {

Просмотреть файл

@ -8,7 +8,7 @@ package protocol
import "fmt" import "fmt"
// SemanticTypes to use in case there is no client, as in the command line, or tests // SemanticTypes to use in case there is no client, as in the command line, or tests.
func SemanticTypes() []string { func SemanticTypes() []string {
return semanticTypes[:] return semanticTypes[:]
} }

Просмотреть файл

@ -18,6 +18,9 @@ type Token struct {
type TokenType string type TokenType string
const ( const (
// These are the tokens defined by LSP 3.17, but a client is
// free to send its own set; any tokens that the server emits
// that are not in this set are simply not encoded in the bitfield.
TokNamespace TokenType = "namespace" TokNamespace TokenType = "namespace"
TokType TokenType = "type" TokType TokenType = "type"
TokInterface TokenType = "interface" TokInterface TokenType = "interface"
@ -32,6 +35,10 @@ const (
TokNumber TokenType = "number" TokNumber TokenType = "number"
TokOperator TokenType = "operator" TokOperator TokenType = "operator"
TokMacro TokenType = "macro" // for templates TokMacro TokenType = "macro" // for templates
// not part of LSP 3.17 (even though JS has labels)
// https://github.com/microsoft/vscode-languageserver-node/issues/1422
TokLabel TokenType = "label"
) )
// Encode returns the LSP encoding of a sequence of tokens. // Encode returns the LSP encoding of a sequence of tokens.

Просмотреть файл

@ -337,6 +337,8 @@ func clientCapabilities(cfg EditorConfig) (protocol.ClientCapabilities, error) {
"struct", "typeParameter", "parameter", "variable", "property", "enumMember", "struct", "typeParameter", "parameter", "variable", "property", "enumMember",
"event", "function", "method", "macro", "keyword", "modifier", "comment", "event", "function", "method", "macro", "keyword", "modifier", "comment",
"string", "number", "regexp", "operator", "string", "number", "regexp", "operator",
// Additional types supported by this client:
"label",
} }
capabilities.TextDocument.SemanticTokens.TokenModifiers = []string{ capabilities.TextDocument.SemanticTokens.TokenModifiers = []string{
"declaration", "definition", "readonly", "static", "declaration", "definition", "readonly", "static",

Просмотреть файл

@ -17,3 +17,13 @@ func F() { //@token("F", "function", "definition")
_ = x //@token("x", "variable", "") _ = x //@token("x", "variable", "")
_ = F //@token("F", "function", "") _ = F //@token("F", "function", "")
} }
func _() {
// A goto's label cannot be found by ascending the syntax tree.
goto loop //@ token("goto", "keyword", ""), token("loop", "label", "")
loop: //@token("loop", "label", "definition")
for {
continue loop //@ token("continue", "keyword", ""), token("loop", "label", "")
}
}