golang.org/x/tools/gopls@v0.15.3/internal/golang/highlight.go (about) 1 // Copyright 2019 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 golang 6 7 import ( 8 "context" 9 "fmt" 10 "go/ast" 11 "go/token" 12 "go/types" 13 14 "golang.org/x/tools/go/ast/astutil" 15 "golang.org/x/tools/gopls/internal/cache" 16 "golang.org/x/tools/gopls/internal/file" 17 "golang.org/x/tools/gopls/internal/protocol" 18 "golang.org/x/tools/gopls/internal/util/typesutil" 19 "golang.org/x/tools/internal/event" 20 ) 21 22 func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Range, error) { 23 ctx, done := event.Start(ctx, "golang.Highlight") 24 defer done() 25 26 // We always want fully parsed files for highlight, regardless 27 // of whether the file belongs to a workspace package. 28 pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) 29 if err != nil { 30 return nil, fmt.Errorf("getting package for Highlight: %w", err) 31 } 32 33 pos, err := pgf.PositionPos(position) 34 if err != nil { 35 return nil, err 36 } 37 path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos) 38 if len(path) == 0 { 39 return nil, fmt.Errorf("no enclosing position found for %v:%v", position.Line, position.Character) 40 } 41 // If start == end for astutil.PathEnclosingInterval, the 1-char interval 42 // following start is used instead. As a result, we might not get an exact 43 // match so we should check the 1-char interval to the left of the passed 44 // in position to see if that is an exact match. 45 if _, ok := path[0].(*ast.Ident); !ok { 46 if p, _ := astutil.PathEnclosingInterval(pgf.File, pos-1, pos-1); p != nil { 47 switch p[0].(type) { 48 case *ast.Ident, *ast.SelectorExpr: 49 path = p // use preceding ident/selector 50 } 51 } 52 } 53 result, err := highlightPath(path, pgf.File, pkg.GetTypesInfo()) 54 if err != nil { 55 return nil, err 56 } 57 var ranges []protocol.Range 58 for rng := range result { 59 rng, err := pgf.PosRange(rng.start, rng.end) 60 if err != nil { 61 return nil, err 62 } 63 ranges = append(ranges, rng) 64 } 65 return ranges, nil 66 } 67 68 // highlightPath returns ranges to highlight for the given enclosing path, 69 // which should be the result of astutil.PathEnclosingInterval. 70 func highlightPath(path []ast.Node, file *ast.File, info *types.Info) (map[posRange]struct{}, error) { 71 result := make(map[posRange]struct{}) 72 switch node := path[0].(type) { 73 case *ast.BasicLit: 74 // Import path string literal? 75 if len(path) > 1 { 76 if imp, ok := path[1].(*ast.ImportSpec); ok { 77 highlight := func(n ast.Node) { 78 result[posRange{start: n.Pos(), end: n.End()}] = struct{}{} 79 } 80 81 // Highlight the import itself... 82 highlight(imp) 83 84 // ...and all references to it in the file. 85 if pkgname, ok := typesutil.ImportedPkgName(info, imp); ok { 86 ast.Inspect(file, func(n ast.Node) bool { 87 if id, ok := n.(*ast.Ident); ok && 88 info.Uses[id] == pkgname { 89 highlight(id) 90 } 91 return true 92 }) 93 } 94 return result, nil 95 } 96 } 97 highlightFuncControlFlow(path, result) 98 case *ast.ReturnStmt, *ast.FuncDecl, *ast.FuncType: 99 highlightFuncControlFlow(path, result) 100 case *ast.Ident: 101 // Check if ident is inside return or func decl. 102 highlightFuncControlFlow(path, result) 103 highlightIdentifier(node, file, info, result) 104 case *ast.ForStmt, *ast.RangeStmt: 105 highlightLoopControlFlow(path, info, result) 106 case *ast.SwitchStmt: 107 highlightSwitchFlow(path, info, result) 108 case *ast.BranchStmt: 109 // BREAK can exit a loop, switch or select, while CONTINUE exit a loop so 110 // these need to be handled separately. They can also be embedded in any 111 // other loop/switch/select if they have a label. TODO: add support for 112 // GOTO and FALLTHROUGH as well. 113 switch node.Tok { 114 case token.BREAK: 115 if node.Label != nil { 116 highlightLabeledFlow(path, info, node, result) 117 } else { 118 highlightUnlabeledBreakFlow(path, info, result) 119 } 120 case token.CONTINUE: 121 if node.Label != nil { 122 highlightLabeledFlow(path, info, node, result) 123 } else { 124 highlightLoopControlFlow(path, info, result) 125 } 126 } 127 default: 128 // If the cursor is in an unidentified area, return empty results. 129 return nil, nil 130 } 131 return result, nil 132 } 133 134 type posRange struct { 135 start, end token.Pos 136 } 137 138 // highlightFuncControlFlow adds highlight ranges to the result map to 139 // associate results and result parameters. 140 // 141 // Specifically, if the cursor is in a result or result parameter, all 142 // results and result parameters with the same index are highlighted. If the 143 // cursor is in a 'func' or 'return' keyword, the func keyword as well as all 144 // returns from that func are highlighted. 145 // 146 // As a special case, if the cursor is within a complicated expression, control 147 // flow highlighting is disabled, as it would highlight too much. 148 func highlightFuncControlFlow(path []ast.Node, result map[posRange]unit) { 149 150 var ( 151 funcType *ast.FuncType // type of enclosing func, or nil 152 funcBody *ast.BlockStmt // body of enclosing func, or nil 153 returnStmt *ast.ReturnStmt // enclosing ReturnStmt within the func, or nil 154 ) 155 156 findEnclosingFunc: 157 for i, n := range path { 158 switch n := n.(type) { 159 // TODO(rfindley, low priority): these pre-existing cases for KeyValueExpr 160 // and CallExpr appear to avoid highlighting when the cursor is in a 161 // complicated expression. However, the basis for this heuristic is 162 // unclear. Can we formalize a rationale? 163 case *ast.KeyValueExpr: 164 // If cursor is in a key: value expr, we don't want control flow highlighting. 165 return 166 167 case *ast.CallExpr: 168 // If cursor is an arg in a callExpr, we don't want control flow highlighting. 169 if i > 0 { 170 for _, arg := range n.Args { 171 if arg == path[i-1] { 172 return 173 } 174 } 175 } 176 177 case *ast.FuncLit: 178 funcType = n.Type 179 funcBody = n.Body 180 break findEnclosingFunc 181 182 case *ast.FuncDecl: 183 funcType = n.Type 184 funcBody = n.Body 185 break findEnclosingFunc 186 187 case *ast.ReturnStmt: 188 returnStmt = n 189 } 190 } 191 192 if funcType == nil { 193 return // cursor is not in a function 194 } 195 196 // Helper functions for inspecting the current location. 197 var ( 198 pos = path[0].Pos() 199 inSpan = func(start, end token.Pos) bool { return start <= pos && pos < end } 200 inNode = func(n ast.Node) bool { return inSpan(n.Pos(), n.End()) } 201 ) 202 203 inResults := funcType.Results != nil && inNode(funcType.Results) 204 205 // If the cursor is on a "return" or "func" keyword, but not highlighting any 206 // specific field or expression, we should highlight all of the exit points 207 // of the function, including the "return" and "func" keywords. 208 funcEnd := funcType.Func + token.Pos(len("func")) 209 highlightAll := path[0] == returnStmt || inSpan(funcType.Func, funcEnd) 210 var highlightIndexes map[int]bool 211 212 if highlightAll { 213 // Add the "func" part of the func declaration. 214 result[posRange{ 215 start: funcType.Func, 216 end: funcEnd, 217 }] = unit{} 218 } else if returnStmt == nil && !inResults { 219 return // nothing to highlight 220 } else { 221 // If we're not highighting the entire return statement, we need to collect 222 // specific result indexes to highlight. This may be more than one index if 223 // the cursor is on a multi-name result field, but not in any specific name. 224 if !highlightAll { 225 highlightIndexes = make(map[int]bool) 226 if returnStmt != nil { 227 for i, n := range returnStmt.Results { 228 if inNode(n) { 229 highlightIndexes[i] = true 230 break 231 } 232 } 233 } 234 235 if funcType.Results != nil { 236 // Scan fields, either adding highlights according to the highlightIndexes 237 // computed above, or accounting for the cursor position within the result 238 // list. 239 // (We do both at once to avoid repeating the cumbersome field traversal.) 240 i := 0 241 findField: 242 for _, field := range funcType.Results.List { 243 for j, name := range field.Names { 244 if inNode(name) || highlightIndexes[i+j] { 245 result[posRange{name.Pos(), name.End()}] = unit{} 246 highlightIndexes[i+j] = true 247 break findField // found/highlighted the specific name 248 } 249 } 250 // If the cursor is in a field but not in a name (e.g. in the space, or 251 // the type), highlight the whole field. 252 // 253 // Note that this may not be ideal if we're at e.g. 254 // 255 // (x,‸y int, z int8) 256 // 257 // ...where it would make more sense to highlight only y. But we don't 258 // reach this function if not in a func, return, ident, or basiclit. 259 if inNode(field) || highlightIndexes[i] { 260 result[posRange{field.Pos(), field.End()}] = unit{} 261 highlightIndexes[i] = true 262 if inNode(field) { 263 for j := range field.Names { 264 highlightIndexes[i+j] = true 265 } 266 } 267 break findField // found/highlighted the field 268 } 269 270 n := len(field.Names) 271 if n == 0 { 272 n = 1 273 } 274 i += n 275 } 276 } 277 } 278 } 279 280 if funcBody != nil { 281 ast.Inspect(funcBody, func(n ast.Node) bool { 282 switch n := n.(type) { 283 case *ast.FuncDecl, *ast.FuncLit: 284 // Don't traverse into any functions other than enclosingFunc. 285 return false 286 case *ast.ReturnStmt: 287 if highlightAll { 288 // Add the entire return statement. 289 result[posRange{n.Pos(), n.End()}] = unit{} 290 } else { 291 // Add the highlighted indexes. 292 for i, expr := range n.Results { 293 if highlightIndexes[i] { 294 result[posRange{expr.Pos(), expr.End()}] = unit{} 295 } 296 } 297 } 298 return false 299 300 } 301 return true 302 }) 303 } 304 } 305 306 // highlightUnlabeledBreakFlow highlights the innermost enclosing for/range/switch or swlect 307 func highlightUnlabeledBreakFlow(path []ast.Node, info *types.Info, result map[posRange]struct{}) { 308 // Reverse walk the path until we find closest loop, select, or switch. 309 for _, n := range path { 310 switch n.(type) { 311 case *ast.ForStmt, *ast.RangeStmt: 312 highlightLoopControlFlow(path, info, result) 313 return // only highlight the innermost statement 314 case *ast.SwitchStmt: 315 highlightSwitchFlow(path, info, result) 316 return 317 case *ast.SelectStmt: 318 // TODO: add highlight when breaking a select. 319 return 320 } 321 } 322 } 323 324 // highlightLabeledFlow highlights the enclosing labeled for, range, 325 // or switch statement denoted by a labeled break or continue stmt. 326 func highlightLabeledFlow(path []ast.Node, info *types.Info, stmt *ast.BranchStmt, result map[posRange]struct{}) { 327 use := info.Uses[stmt.Label] 328 if use == nil { 329 return 330 } 331 for _, n := range path { 332 if label, ok := n.(*ast.LabeledStmt); ok && info.Defs[label.Label] == use { 333 switch label.Stmt.(type) { 334 case *ast.ForStmt, *ast.RangeStmt: 335 highlightLoopControlFlow([]ast.Node{label.Stmt, label}, info, result) 336 case *ast.SwitchStmt: 337 highlightSwitchFlow([]ast.Node{label.Stmt, label}, info, result) 338 } 339 return 340 } 341 } 342 } 343 344 func labelFor(path []ast.Node) *ast.Ident { 345 if len(path) > 1 { 346 if n, ok := path[1].(*ast.LabeledStmt); ok { 347 return n.Label 348 } 349 } 350 return nil 351 } 352 353 func highlightLoopControlFlow(path []ast.Node, info *types.Info, result map[posRange]struct{}) { 354 var loop ast.Node 355 var loopLabel *ast.Ident 356 stmtLabel := labelFor(path) 357 Outer: 358 // Reverse walk the path till we get to the for loop. 359 for i := range path { 360 switch n := path[i].(type) { 361 case *ast.ForStmt, *ast.RangeStmt: 362 loopLabel = labelFor(path[i:]) 363 364 if stmtLabel == nil || loopLabel == stmtLabel { 365 loop = n 366 break Outer 367 } 368 } 369 } 370 if loop == nil { 371 return 372 } 373 374 // Add the for statement. 375 rng := posRange{ 376 start: loop.Pos(), 377 end: loop.Pos() + token.Pos(len("for")), 378 } 379 result[rng] = struct{}{} 380 381 // Traverse AST to find branch statements within the same for-loop. 382 ast.Inspect(loop, func(n ast.Node) bool { 383 switch n.(type) { 384 case *ast.ForStmt, *ast.RangeStmt: 385 return loop == n 386 case *ast.SwitchStmt, *ast.SelectStmt: 387 return false 388 } 389 b, ok := n.(*ast.BranchStmt) 390 if !ok { 391 return true 392 } 393 if b.Label == nil || info.Uses[b.Label] == info.Defs[loopLabel] { 394 result[posRange{start: b.Pos(), end: b.End()}] = struct{}{} 395 } 396 return true 397 }) 398 399 // Find continue statements in the same loop or switches/selects. 400 ast.Inspect(loop, func(n ast.Node) bool { 401 switch n.(type) { 402 case *ast.ForStmt, *ast.RangeStmt: 403 return loop == n 404 } 405 406 if n, ok := n.(*ast.BranchStmt); ok && n.Tok == token.CONTINUE { 407 result[posRange{start: n.Pos(), end: n.End()}] = struct{}{} 408 } 409 return true 410 }) 411 412 // We don't need to check other for loops if we aren't looking for labeled statements. 413 if loopLabel == nil { 414 return 415 } 416 417 // Find labeled branch statements in any loop. 418 ast.Inspect(loop, func(n ast.Node) bool { 419 b, ok := n.(*ast.BranchStmt) 420 if !ok { 421 return true 422 } 423 // statement with labels that matches the loop 424 if b.Label != nil && info.Uses[b.Label] == info.Defs[loopLabel] { 425 result[posRange{start: b.Pos(), end: b.End()}] = struct{}{} 426 } 427 return true 428 }) 429 } 430 431 func highlightSwitchFlow(path []ast.Node, info *types.Info, result map[posRange]struct{}) { 432 var switchNode ast.Node 433 var switchNodeLabel *ast.Ident 434 stmtLabel := labelFor(path) 435 Outer: 436 // Reverse walk the path till we get to the switch statement. 437 for i := range path { 438 switch n := path[i].(type) { 439 case *ast.SwitchStmt: 440 switchNodeLabel = labelFor(path[i:]) 441 if stmtLabel == nil || switchNodeLabel == stmtLabel { 442 switchNode = n 443 break Outer 444 } 445 } 446 } 447 // Cursor is not in a switch statement 448 if switchNode == nil { 449 return 450 } 451 452 // Add the switch statement. 453 rng := posRange{ 454 start: switchNode.Pos(), 455 end: switchNode.Pos() + token.Pos(len("switch")), 456 } 457 result[rng] = struct{}{} 458 459 // Traverse AST to find break statements within the same switch. 460 ast.Inspect(switchNode, func(n ast.Node) bool { 461 switch n.(type) { 462 case *ast.SwitchStmt: 463 return switchNode == n 464 case *ast.ForStmt, *ast.RangeStmt, *ast.SelectStmt: 465 return false 466 } 467 468 b, ok := n.(*ast.BranchStmt) 469 if !ok || b.Tok != token.BREAK { 470 return true 471 } 472 473 if b.Label == nil || info.Uses[b.Label] == info.Defs[switchNodeLabel] { 474 result[posRange{start: b.Pos(), end: b.End()}] = struct{}{} 475 } 476 return true 477 }) 478 479 // We don't need to check other switches if we aren't looking for labeled statements. 480 if switchNodeLabel == nil { 481 return 482 } 483 484 // Find labeled break statements in any switch 485 ast.Inspect(switchNode, func(n ast.Node) bool { 486 b, ok := n.(*ast.BranchStmt) 487 if !ok || b.Tok != token.BREAK { 488 return true 489 } 490 491 if b.Label != nil && info.Uses[b.Label] == info.Defs[switchNodeLabel] { 492 result[posRange{start: b.Pos(), end: b.End()}] = struct{}{} 493 } 494 495 return true 496 }) 497 } 498 499 func highlightIdentifier(id *ast.Ident, file *ast.File, info *types.Info, result map[posRange]struct{}) { 500 highlight := func(n ast.Node) { 501 result[posRange{start: n.Pos(), end: n.End()}] = struct{}{} 502 } 503 504 // obj may be nil if the Ident is undefined. 505 // In this case, the behavior expected by tests is 506 // to match other undefined Idents of the same name. 507 obj := info.ObjectOf(id) 508 509 ast.Inspect(file, func(n ast.Node) bool { 510 switch n := n.(type) { 511 case *ast.Ident: 512 if n.Name == id.Name && info.ObjectOf(n) == obj { 513 highlight(n) 514 } 515 516 case *ast.ImportSpec: 517 pkgname, ok := typesutil.ImportedPkgName(info, n) 518 if ok && pkgname == obj { 519 if n.Name != nil { 520 highlight(n.Name) 521 } else { 522 highlight(n) 523 } 524 } 525 } 526 return true 527 }) 528 }