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  }