golang.org/x/tools/gopls@v0.15.3/internal/golang/semtok.go (about)

     1  // Copyright 2024 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  // This file defines the Semantic Tokens operation for Go source.
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"errors"
    13  	"fmt"
    14  	"go/ast"
    15  	"go/token"
    16  	"go/types"
    17  	"log"
    18  	"path/filepath"
    19  	"strings"
    20  	"time"
    21  
    22  	"golang.org/x/tools/gopls/internal/cache"
    23  	"golang.org/x/tools/gopls/internal/cache/metadata"
    24  	"golang.org/x/tools/gopls/internal/file"
    25  	"golang.org/x/tools/gopls/internal/protocol"
    26  	"golang.org/x/tools/gopls/internal/protocol/semtok"
    27  	"golang.org/x/tools/gopls/internal/util/safetoken"
    28  	"golang.org/x/tools/internal/event"
    29  )
    30  
    31  // to control comprehensive logging of decisions (gopls semtok foo.go > /dev/null shows log output)
    32  // semDebug should NEVER be true in checked-in code
    33  const semDebug = false
    34  
    35  // The LSP says that errors for the semantic token requests should only be returned
    36  // for exceptions (a word not otherwise defined). This code treats a too-large file
    37  // as an exception. On parse errors, the code does what it can.
    38  
    39  // reject full semantic token requests for large files
    40  const maxFullFileSize int = 100000
    41  
    42  func SemanticTokens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng *protocol.Range) (*protocol.SemanticTokens, error) {
    43  	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	// Select range.
    49  	var start, end token.Pos
    50  	if rng != nil {
    51  		var err error
    52  		start, end, err = pgf.RangePos(*rng)
    53  		if err != nil {
    54  			return nil, err // e.g. invalid range
    55  		}
    56  	} else {
    57  		tok := pgf.Tok
    58  		start, end = tok.Pos(0), tok.Pos(tok.Size()) // entire file
    59  	}
    60  	if int(end-start) > maxFullFileSize {
    61  		err := fmt.Errorf("semantic tokens: range %s too large (%d > %d)",
    62  			fh.URI().Path(), end-start, maxFullFileSize)
    63  		return nil, err
    64  	}
    65  
    66  	tv := &tokenVisitor{
    67  		ctx:            ctx,
    68  		metadataSource: snapshot,
    69  		pgf:            pgf,
    70  		start:          start,
    71  		end:            end,
    72  		ti:             pkg.GetTypesInfo(),
    73  		pkg:            pkg,
    74  		fset:           pkg.FileSet(),
    75  	}
    76  	tv.visit()
    77  	return &protocol.SemanticTokens{
    78  		Data: semtok.Encode(
    79  			tv.items,
    80  			snapshot.Options().NoSemanticString,
    81  			snapshot.Options().NoSemanticNumber,
    82  			snapshot.Options().SemanticTypes,
    83  			snapshot.Options().SemanticMods),
    84  		// For delta requests, but we've never seen any.
    85  		ResultID: time.Now().String(),
    86  	}, nil
    87  }
    88  
    89  type tokenVisitor struct {
    90  	items []semtok.Token
    91  	ctx   context.Context // for event logging
    92  	// metadataSource is used to resolve imports
    93  	metadataSource metadata.Source
    94  	pgf            *ParsedGoFile
    95  	start, end     token.Pos // range of interest
    96  	ti             *types.Info
    97  	pkg            *cache.Package
    98  	fset           *token.FileSet
    99  	// path from the root of the parse tree, used for debugging
   100  	stack []ast.Node
   101  }
   102  
   103  func (tv *tokenVisitor) visit() {
   104  	f := tv.pgf.File
   105  	// may not be in range, but harmless
   106  	tv.token(f.Package, len("package"), semtok.TokKeyword, nil)
   107  	tv.token(f.Name.NamePos, len(f.Name.Name), semtok.TokNamespace, nil)
   108  	inspect := func(n ast.Node) bool {
   109  		return tv.inspector(n)
   110  	}
   111  	for _, d := range f.Decls {
   112  		// only look at the decls that overlap the range
   113  		start, end := d.Pos(), d.End()
   114  		if end <= tv.start || start >= tv.end {
   115  			continue
   116  		}
   117  		ast.Inspect(d, inspect)
   118  	}
   119  	for _, cg := range f.Comments {
   120  		for _, c := range cg.List {
   121  			if strings.HasPrefix(c.Text, "//go:") {
   122  				tv.godirective(c)
   123  				continue
   124  			}
   125  			if !strings.Contains(c.Text, "\n") {
   126  				tv.token(c.Pos(), len(c.Text), semtok.TokComment, nil)
   127  				continue
   128  			}
   129  			tv.multiline(c.Pos(), c.End(), c.Text, semtok.TokComment)
   130  		}
   131  	}
   132  }
   133  
   134  func (tv *tokenVisitor) token(start token.Pos, leng int, typ semtok.TokenType, mods []string) {
   135  	if leng <= 0 {
   136  		return // vscode doesn't like 0-length Tokens
   137  	}
   138  	if !start.IsValid() {
   139  		// This is not worth reporting. TODO(pjw): does it still happen?
   140  		return
   141  	}
   142  	if start >= tv.end || start+token.Pos(leng) <= tv.start {
   143  		return
   144  	}
   145  	// want a line and column from start (in LSP coordinates). Ignore line directives.
   146  	lspRange, err := tv.pgf.PosRange(start, start+token.Pos(leng))
   147  	if err != nil {
   148  		event.Error(tv.ctx, "failed to convert to range", err)
   149  		return
   150  	}
   151  	if lspRange.End.Line != lspRange.Start.Line {
   152  		// this happens if users are typing at the end of the file, but report nothing
   153  		return
   154  	}
   155  	tv.items = append(tv.items, semtok.Token{
   156  		Line:      lspRange.Start.Line,
   157  		Start:     lspRange.Start.Character,
   158  		Len:       lspRange.End.Character - lspRange.Start.Character, // all on one line
   159  		Type:      typ,
   160  		Modifiers: mods,
   161  	})
   162  }
   163  
   164  // convert the stack to a string, for debugging
   165  func (tv *tokenVisitor) strStack() string {
   166  	msg := []string{"["}
   167  	for i := len(tv.stack) - 1; i >= 0; i-- {
   168  		s := tv.stack[i]
   169  		msg = append(msg, fmt.Sprintf("%T", s)[5:])
   170  	}
   171  	if len(tv.stack) > 0 {
   172  		loc := tv.stack[len(tv.stack)-1].Pos()
   173  		if _, err := safetoken.Offset(tv.pgf.Tok, loc); err != nil {
   174  			msg = append(msg, fmt.Sprintf("invalid position %v for %s", loc, tv.pgf.URI))
   175  		} else {
   176  			add := safetoken.Position(tv.pgf.Tok, loc)
   177  			nm := filepath.Base(add.Filename)
   178  			msg = append(msg, fmt.Sprintf("(%s:%d,col:%d)", nm, add.Line, add.Column))
   179  		}
   180  	}
   181  	msg = append(msg, "]")
   182  	return strings.Join(msg, " ")
   183  }
   184  
   185  // find the line in the source
   186  func (tv *tokenVisitor) srcLine(x ast.Node) string {
   187  	file := tv.pgf.Tok
   188  	line := safetoken.Line(file, x.Pos())
   189  	start, err := safetoken.Offset(file, file.LineStart(line))
   190  	if err != nil {
   191  		return ""
   192  	}
   193  	end := start
   194  	for ; end < len(tv.pgf.Src) && tv.pgf.Src[end] != '\n'; end++ {
   195  
   196  	}
   197  	ans := tv.pgf.Src[start:end]
   198  	return string(ans)
   199  }
   200  
   201  func (tv *tokenVisitor) inspector(n ast.Node) bool {
   202  	pop := func() {
   203  		tv.stack = tv.stack[:len(tv.stack)-1]
   204  	}
   205  	if n == nil {
   206  		pop()
   207  		return true
   208  	}
   209  	tv.stack = append(tv.stack, n)
   210  	switch x := n.(type) {
   211  	case *ast.ArrayType:
   212  	case *ast.AssignStmt:
   213  		tv.token(x.TokPos, len(x.Tok.String()), semtok.TokOperator, nil)
   214  	case *ast.BasicLit:
   215  		if strings.Contains(x.Value, "\n") {
   216  			// has to be a string.
   217  			tv.multiline(x.Pos(), x.End(), x.Value, semtok.TokString)
   218  			break
   219  		}
   220  		ln := len(x.Value)
   221  		what := semtok.TokNumber
   222  		if x.Kind == token.STRING {
   223  			what = semtok.TokString
   224  		}
   225  		tv.token(x.Pos(), ln, what, nil)
   226  	case *ast.BinaryExpr:
   227  		tv.token(x.OpPos, len(x.Op.String()), semtok.TokOperator, nil)
   228  	case *ast.BlockStmt:
   229  	case *ast.BranchStmt:
   230  		tv.token(x.TokPos, len(x.Tok.String()), semtok.TokKeyword, nil)
   231  		// There's no semantic encoding for labels
   232  	case *ast.CallExpr:
   233  		if x.Ellipsis != token.NoPos {
   234  			tv.token(x.Ellipsis, len("..."), semtok.TokOperator, nil)
   235  		}
   236  	case *ast.CaseClause:
   237  		iam := "case"
   238  		if x.List == nil {
   239  			iam = "default"
   240  		}
   241  		tv.token(x.Case, len(iam), semtok.TokKeyword, nil)
   242  	case *ast.ChanType:
   243  		// chan | chan <- | <- chan
   244  		switch {
   245  		case x.Arrow == token.NoPos:
   246  			tv.token(x.Begin, len("chan"), semtok.TokKeyword, nil)
   247  		case x.Arrow == x.Begin:
   248  			tv.token(x.Arrow, 2, semtok.TokOperator, nil)
   249  			pos := tv.findKeyword("chan", x.Begin+2, x.Value.Pos())
   250  			tv.token(pos, len("chan"), semtok.TokKeyword, nil)
   251  		case x.Arrow != x.Begin:
   252  			tv.token(x.Begin, len("chan"), semtok.TokKeyword, nil)
   253  			tv.token(x.Arrow, 2, semtok.TokOperator, nil)
   254  		}
   255  	case *ast.CommClause:
   256  		iam := len("case")
   257  		if x.Comm == nil {
   258  			iam = len("default")
   259  		}
   260  		tv.token(x.Case, iam, semtok.TokKeyword, nil)
   261  	case *ast.CompositeLit:
   262  	case *ast.DeclStmt:
   263  	case *ast.DeferStmt:
   264  		tv.token(x.Defer, len("defer"), semtok.TokKeyword, nil)
   265  	case *ast.Ellipsis:
   266  		tv.token(x.Ellipsis, len("..."), semtok.TokOperator, nil)
   267  	case *ast.EmptyStmt:
   268  	case *ast.ExprStmt:
   269  	case *ast.Field:
   270  	case *ast.FieldList:
   271  	case *ast.ForStmt:
   272  		tv.token(x.For, len("for"), semtok.TokKeyword, nil)
   273  	case *ast.FuncDecl:
   274  	case *ast.FuncLit:
   275  	case *ast.FuncType:
   276  		if x.Func != token.NoPos {
   277  			tv.token(x.Func, len("func"), semtok.TokKeyword, nil)
   278  		}
   279  	case *ast.GenDecl:
   280  		tv.token(x.TokPos, len(x.Tok.String()), semtok.TokKeyword, nil)
   281  	case *ast.GoStmt:
   282  		tv.token(x.Go, len("go"), semtok.TokKeyword, nil)
   283  	case *ast.Ident:
   284  		tv.ident(x)
   285  	case *ast.IfStmt:
   286  		tv.token(x.If, len("if"), semtok.TokKeyword, nil)
   287  		if x.Else != nil {
   288  			// x.Body.End() or x.Body.End()+1, not that it matters
   289  			pos := tv.findKeyword("else", x.Body.End(), x.Else.Pos())
   290  			tv.token(pos, len("else"), semtok.TokKeyword, nil)
   291  		}
   292  	case *ast.ImportSpec:
   293  		tv.importSpec(x)
   294  		pop()
   295  		return false
   296  	case *ast.IncDecStmt:
   297  		tv.token(x.TokPos, len(x.Tok.String()), semtok.TokOperator, nil)
   298  	case *ast.IndexExpr:
   299  	case *ast.IndexListExpr:
   300  	case *ast.InterfaceType:
   301  		tv.token(x.Interface, len("interface"), semtok.TokKeyword, nil)
   302  	case *ast.KeyValueExpr:
   303  	case *ast.LabeledStmt:
   304  	case *ast.MapType:
   305  		tv.token(x.Map, len("map"), semtok.TokKeyword, nil)
   306  	case *ast.ParenExpr:
   307  	case *ast.RangeStmt:
   308  		tv.token(x.For, len("for"), semtok.TokKeyword, nil)
   309  		// x.TokPos == token.NoPos is legal (for range foo {})
   310  		offset := x.TokPos
   311  		if offset == token.NoPos {
   312  			offset = x.For
   313  		}
   314  		pos := tv.findKeyword("range", offset, x.X.Pos())
   315  		tv.token(pos, len("range"), semtok.TokKeyword, nil)
   316  	case *ast.ReturnStmt:
   317  		tv.token(x.Return, len("return"), semtok.TokKeyword, nil)
   318  	case *ast.SelectStmt:
   319  		tv.token(x.Select, len("select"), semtok.TokKeyword, nil)
   320  	case *ast.SelectorExpr:
   321  	case *ast.SendStmt:
   322  		tv.token(x.Arrow, len("<-"), semtok.TokOperator, nil)
   323  	case *ast.SliceExpr:
   324  	case *ast.StarExpr:
   325  		tv.token(x.Star, len("*"), semtok.TokOperator, nil)
   326  	case *ast.StructType:
   327  		tv.token(x.Struct, len("struct"), semtok.TokKeyword, nil)
   328  	case *ast.SwitchStmt:
   329  		tv.token(x.Switch, len("switch"), semtok.TokKeyword, nil)
   330  	case *ast.TypeAssertExpr:
   331  		if x.Type == nil {
   332  			pos := tv.findKeyword("type", x.Lparen, x.Rparen)
   333  			tv.token(pos, len("type"), semtok.TokKeyword, nil)
   334  		}
   335  	case *ast.TypeSpec:
   336  	case *ast.TypeSwitchStmt:
   337  		tv.token(x.Switch, len("switch"), semtok.TokKeyword, nil)
   338  	case *ast.UnaryExpr:
   339  		tv.token(x.OpPos, len(x.Op.String()), semtok.TokOperator, nil)
   340  	case *ast.ValueSpec:
   341  	// things only seen with parsing or type errors, so ignore them
   342  	case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
   343  		return true
   344  	// not going to see these
   345  	case *ast.File, *ast.Package:
   346  		tv.unexpected(fmt.Sprintf("implement %T %s", x, safetoken.Position(tv.pgf.Tok, x.Pos())))
   347  	// other things we knowingly ignore
   348  	case *ast.Comment, *ast.CommentGroup:
   349  		pop()
   350  		return false
   351  	default:
   352  		tv.unexpected(fmt.Sprintf("failed to implement %T", x))
   353  	}
   354  	return true
   355  }
   356  
   357  func (tv *tokenVisitor) ident(x *ast.Ident) {
   358  	if tv.ti == nil {
   359  		what, mods := tv.unkIdent(x)
   360  		if what != "" {
   361  			tv.token(x.Pos(), len(x.String()), what, mods)
   362  		}
   363  		if semDebug {
   364  			log.Printf(" nil %s/nil/nil %q %v %s", x.String(), what, mods, tv.strStack())
   365  		}
   366  		return
   367  	}
   368  	def := tv.ti.Defs[x]
   369  	if def != nil {
   370  		what, mods := tv.definitionFor(x, def)
   371  		if what != "" {
   372  			tv.token(x.Pos(), len(x.String()), what, mods)
   373  		}
   374  		if semDebug {
   375  			log.Printf(" for %s/%T/%T got %s %v (%s)", x.String(), def, def.Type(), what, mods, tv.strStack())
   376  		}
   377  		return
   378  	}
   379  	use := tv.ti.Uses[x]
   380  	tok := func(pos token.Pos, lng int, tok semtok.TokenType, mods []string) {
   381  		tv.token(pos, lng, tok, mods)
   382  		q := "nil"
   383  		if use != nil {
   384  			q = fmt.Sprintf("%T", use.Type())
   385  		}
   386  		if semDebug {
   387  			log.Printf(" use %s/%T/%s got %s %v (%s)", x.String(), use, q, tok, mods, tv.strStack())
   388  		}
   389  	}
   390  
   391  	switch y := use.(type) {
   392  	case nil:
   393  		what, mods := tv.unkIdent(x)
   394  		if what != "" {
   395  			tok(x.Pos(), len(x.String()), what, mods)
   396  		} else if semDebug {
   397  			// tok() wasn't called, so didn't log
   398  			log.Printf(" nil %s/%T/nil %q %v (%s)", x.String(), use, what, mods, tv.strStack())
   399  		}
   400  		return
   401  	case *types.Builtin:
   402  		tok(x.NamePos, len(x.Name), semtok.TokFunction, []string{"defaultLibrary"})
   403  	case *types.Const:
   404  		mods := []string{"readonly"}
   405  		tt := y.Type()
   406  		if _, ok := tt.(*types.Basic); ok {
   407  			tok(x.Pos(), len(x.String()), semtok.TokVariable, mods)
   408  			break
   409  		}
   410  		if ttx, ok := tt.(*types.Named); ok {
   411  			if x.String() == "iota" {
   412  				tv.unexpected(fmt.Sprintf("iota:%T", ttx))
   413  			}
   414  			if _, ok := ttx.Underlying().(*types.Basic); ok {
   415  				tok(x.Pos(), len(x.String()), semtok.TokVariable, mods)
   416  				break
   417  			}
   418  			tv.unexpected(fmt.Sprintf("%q/%T", x.String(), tt))
   419  		}
   420  		// can this happen? Don't think so
   421  		tv.unexpected(fmt.Sprintf("%s %T %#v", x.String(), tt, tt))
   422  	case *types.Func:
   423  		tok(x.Pos(), len(x.Name), semtok.TokFunction, nil)
   424  	case *types.Label:
   425  		// nothing to map it to
   426  	case *types.Nil:
   427  		// nil is a predeclared identifier
   428  		tok(x.Pos(), len("nil"), semtok.TokVariable, []string{"readonly", "defaultLibrary"})
   429  	case *types.PkgName:
   430  		tok(x.Pos(), len(x.Name), semtok.TokNamespace, nil)
   431  	case *types.TypeName: // could be a TokTypeParam
   432  		var mods []string
   433  		if _, ok := y.Type().(*types.Basic); ok {
   434  			mods = []string{"defaultLibrary"}
   435  		} else if _, ok := y.Type().(*types.TypeParam); ok {
   436  			tok(x.Pos(), len(x.String()), semtok.TokTypeParam, mods)
   437  			break
   438  		}
   439  		tok(x.Pos(), len(x.String()), semtok.TokType, mods)
   440  	case *types.Var:
   441  		if isSignature(y) {
   442  			tok(x.Pos(), len(x.Name), semtok.TokFunction, nil)
   443  		} else if tv.isParam(use.Pos()) {
   444  			// variable, unless use.pos is the pos of a Field in an ancestor FuncDecl
   445  			// or FuncLit and then it's a parameter
   446  			tok(x.Pos(), len(x.Name), semtok.TokParameter, nil)
   447  		} else {
   448  			tok(x.Pos(), len(x.Name), semtok.TokVariable, nil)
   449  		}
   450  
   451  	default:
   452  		// can't happen
   453  		if use == nil {
   454  			msg := fmt.Sprintf("%#v %#v %#v", x, tv.ti.Defs[x], tv.ti.Uses[x])
   455  			tv.unexpected(msg)
   456  		}
   457  		if use.Type() != nil {
   458  			tv.unexpected(fmt.Sprintf("%s %T/%T,%#v", x.String(), use, use.Type(), use))
   459  		} else {
   460  			tv.unexpected(fmt.Sprintf("%s %T", x.String(), use))
   461  		}
   462  	}
   463  }
   464  
   465  func (tv *tokenVisitor) isParam(pos token.Pos) bool {
   466  	for i := len(tv.stack) - 1; i >= 0; i-- {
   467  		switch n := tv.stack[i].(type) {
   468  		case *ast.FuncDecl:
   469  			for _, f := range n.Type.Params.List {
   470  				for _, id := range f.Names {
   471  					if id.Pos() == pos {
   472  						return true
   473  					}
   474  				}
   475  			}
   476  		case *ast.FuncLit:
   477  			for _, f := range n.Type.Params.List {
   478  				for _, id := range f.Names {
   479  					if id.Pos() == pos {
   480  						return true
   481  					}
   482  				}
   483  			}
   484  		}
   485  	}
   486  	return false
   487  }
   488  
   489  func isSignature(use types.Object) bool {
   490  	if _, ok := use.(*types.Var); !ok {
   491  		return false
   492  	}
   493  	v := use.Type()
   494  	if v == nil {
   495  		return false
   496  	}
   497  	if _, ok := v.(*types.Signature); ok {
   498  		return true
   499  	}
   500  	return false
   501  }
   502  
   503  // both tv.ti.Defs and tv.ti.Uses are nil. use the parse stack.
   504  // a lot of these only happen when the package doesn't compile
   505  // but in that case it is all best-effort from the parse tree
   506  func (tv *tokenVisitor) unkIdent(x *ast.Ident) (semtok.TokenType, []string) {
   507  	def := []string{"definition"}
   508  	n := len(tv.stack) - 2 // parent of Ident
   509  	if n < 0 {
   510  		tv.unexpected("no stack?")
   511  		return "", nil
   512  	}
   513  	switch nd := tv.stack[n].(type) {
   514  	case *ast.BinaryExpr, *ast.UnaryExpr, *ast.ParenExpr, *ast.StarExpr,
   515  		*ast.IncDecStmt, *ast.SliceExpr, *ast.ExprStmt, *ast.IndexExpr,
   516  		*ast.ReturnStmt, *ast.ChanType, *ast.SendStmt,
   517  		*ast.ForStmt,      // possibly incomplete
   518  		*ast.IfStmt,       /* condition */
   519  		*ast.KeyValueExpr, // either key or value
   520  		*ast.IndexListExpr:
   521  		return semtok.TokVariable, nil
   522  	case *ast.Ellipsis:
   523  		return semtok.TokType, nil
   524  	case *ast.CaseClause:
   525  		if n-2 >= 0 {
   526  			if _, ok := tv.stack[n-2].(*ast.TypeSwitchStmt); ok {
   527  				return semtok.TokType, nil
   528  			}
   529  		}
   530  		return semtok.TokVariable, nil
   531  	case *ast.ArrayType:
   532  		if x == nd.Len {
   533  			// or maybe a Type Param, but we can't just from the parse tree
   534  			return semtok.TokVariable, nil
   535  		} else {
   536  			return semtok.TokType, nil
   537  		}
   538  	case *ast.MapType:
   539  		return semtok.TokType, nil
   540  	case *ast.CallExpr:
   541  		if x == nd.Fun {
   542  			return semtok.TokFunction, nil
   543  		}
   544  		return semtok.TokVariable, nil
   545  	case *ast.SwitchStmt:
   546  		return semtok.TokVariable, nil
   547  	case *ast.TypeAssertExpr:
   548  		if x == nd.X {
   549  			return semtok.TokVariable, nil
   550  		} else if x == nd.Type {
   551  			return semtok.TokType, nil
   552  		}
   553  	case *ast.ValueSpec:
   554  		for _, p := range nd.Names {
   555  			if p == x {
   556  				return semtok.TokVariable, def
   557  			}
   558  		}
   559  		for _, p := range nd.Values {
   560  			if p == x {
   561  				return semtok.TokVariable, nil
   562  			}
   563  		}
   564  		return semtok.TokType, nil
   565  	case *ast.SelectorExpr: // e.ti.Selections[nd] is nil, so no help
   566  		if n-1 >= 0 {
   567  			if ce, ok := tv.stack[n-1].(*ast.CallExpr); ok {
   568  				// ... CallExpr SelectorExpr Ident (_.x())
   569  				if ce.Fun == nd && nd.Sel == x {
   570  					return semtok.TokFunction, nil
   571  				}
   572  			}
   573  		}
   574  		return semtok.TokVariable, nil
   575  	case *ast.AssignStmt:
   576  		for _, p := range nd.Lhs {
   577  			// x := ..., or x = ...
   578  			if p == x {
   579  				if nd.Tok != token.DEFINE {
   580  					def = nil
   581  				}
   582  				return semtok.TokVariable, def // '_' in _ = ...
   583  			}
   584  		}
   585  		// RHS, = x
   586  		return semtok.TokVariable, nil
   587  	case *ast.TypeSpec: // it's a type if it is either the Name or the Type
   588  		if x == nd.Type {
   589  			def = nil
   590  		}
   591  		return semtok.TokType, def
   592  	case *ast.Field:
   593  		// ident could be type in a field, or a method in an interface type, or a variable
   594  		if x == nd.Type {
   595  			return semtok.TokType, nil
   596  		}
   597  		if n-2 >= 0 {
   598  			_, okit := tv.stack[n-2].(*ast.InterfaceType)
   599  			_, okfl := tv.stack[n-1].(*ast.FieldList)
   600  			if okit && okfl {
   601  				return semtok.TokMethod, def
   602  			}
   603  		}
   604  		return semtok.TokVariable, nil
   605  	case *ast.LabeledStmt, *ast.BranchStmt:
   606  		// nothing to report
   607  	case *ast.CompositeLit:
   608  		if nd.Type == x {
   609  			return semtok.TokType, nil
   610  		}
   611  		return semtok.TokVariable, nil
   612  	case *ast.RangeStmt:
   613  		if nd.Tok != token.DEFINE {
   614  			def = nil
   615  		}
   616  		return semtok.TokVariable, def
   617  	case *ast.FuncDecl:
   618  		return semtok.TokFunction, def
   619  	default:
   620  		msg := fmt.Sprintf("%T undexpected: %s %s%q", nd, x.Name, tv.strStack(), tv.srcLine(x))
   621  		tv.unexpected(msg)
   622  	}
   623  	return "", nil
   624  }
   625  
   626  func isDeprecated(n *ast.CommentGroup) bool {
   627  	if n == nil {
   628  		return false
   629  	}
   630  	for _, c := range n.List {
   631  		if strings.HasPrefix(c.Text, "// Deprecated") {
   632  			return true
   633  		}
   634  	}
   635  	return false
   636  }
   637  
   638  func (tv *tokenVisitor) definitionFor(x *ast.Ident, def types.Object) (semtok.TokenType, []string) {
   639  	// PJW: def == types.Label? probably a nothing
   640  	// PJW: look into replacing these syntactic tests with types more generally
   641  	mods := []string{"definition"}
   642  	for i := len(tv.stack) - 1; i >= 0; i-- {
   643  		s := tv.stack[i]
   644  		switch y := s.(type) {
   645  		case *ast.AssignStmt, *ast.RangeStmt:
   646  			if x.Name == "_" {
   647  				return "", nil // not really a variable
   648  			}
   649  			return semtok.TokVariable, mods
   650  		case *ast.GenDecl:
   651  			if isDeprecated(y.Doc) {
   652  				mods = append(mods, "deprecated")
   653  			}
   654  			if y.Tok == token.CONST {
   655  				mods = append(mods, "readonly")
   656  			}
   657  			return semtok.TokVariable, mods
   658  		case *ast.FuncDecl:
   659  			// If x is immediately under a FuncDecl, it is a function or method
   660  			if i == len(tv.stack)-2 {
   661  				if isDeprecated(y.Doc) {
   662  					mods = append(mods, "deprecated")
   663  				}
   664  				if y.Recv != nil {
   665  					return semtok.TokMethod, mods
   666  				}
   667  				return semtok.TokFunction, mods
   668  			}
   669  			// if x < ... < FieldList < FuncDecl, this is the receiver, a variable
   670  			// PJW: maybe not. it might be a typeparameter in the type of the receiver
   671  			if _, ok := tv.stack[i+1].(*ast.FieldList); ok {
   672  				if _, ok := def.(*types.TypeName); ok {
   673  					return semtok.TokTypeParam, mods
   674  				}
   675  				return semtok.TokVariable, nil
   676  			}
   677  			// if x < ... < FieldList < FuncType < FuncDecl, this is a param
   678  			return semtok.TokParameter, mods
   679  		case *ast.FuncType: // is it in the TypeParams?
   680  			if isTypeParam(x, y) {
   681  				return semtok.TokTypeParam, mods
   682  			}
   683  			return semtok.TokParameter, mods
   684  		case *ast.InterfaceType:
   685  			return semtok.TokMethod, mods
   686  		case *ast.TypeSpec:
   687  			// GenDecl/Typespec/FuncType/FieldList/Field/Ident
   688  			// (type A func(b uint64)) (err error)
   689  			// b and err should not be semtok.TokType, but semtok.TokVariable
   690  			// and in GenDecl/TpeSpec/StructType/FieldList/Field/Ident
   691  			// (type A struct{b uint64}
   692  			// but on type B struct{C}), C is a type, but is not being defined.
   693  			// GenDecl/TypeSpec/FieldList/Field/Ident is a typeParam
   694  			if _, ok := tv.stack[i+1].(*ast.FieldList); ok {
   695  				return semtok.TokTypeParam, mods
   696  			}
   697  			fldm := tv.stack[len(tv.stack)-2]
   698  			if fld, ok := fldm.(*ast.Field); ok {
   699  				// if len(fld.names) == 0 this is a semtok.TokType, being used
   700  				if len(fld.Names) == 0 {
   701  					return semtok.TokType, nil
   702  				}
   703  				return semtok.TokVariable, mods
   704  			}
   705  			return semtok.TokType, mods
   706  		}
   707  	}
   708  	// can't happen
   709  	msg := fmt.Sprintf("failed to find the decl for %s", safetoken.Position(tv.pgf.Tok, x.Pos()))
   710  	tv.unexpected(msg)
   711  	return "", []string{""}
   712  }
   713  
   714  func isTypeParam(x *ast.Ident, y *ast.FuncType) bool {
   715  	tp := y.TypeParams
   716  	if tp == nil {
   717  		return false
   718  	}
   719  	for _, p := range tp.List {
   720  		for _, n := range p.Names {
   721  			if x == n {
   722  				return true
   723  			}
   724  		}
   725  	}
   726  	return false
   727  }
   728  
   729  func (tv *tokenVisitor) multiline(start, end token.Pos, val string, tok semtok.TokenType) {
   730  	f := tv.fset.File(start)
   731  	// the hard part is finding the lengths of lines. include the \n
   732  	leng := func(line int) int {
   733  		n := f.LineStart(line)
   734  		if line >= f.LineCount() {
   735  			return f.Size() - int(n)
   736  		}
   737  		return int(f.LineStart(line+1) - n)
   738  	}
   739  	spos := safetoken.StartPosition(tv.fset, start)
   740  	epos := safetoken.EndPosition(tv.fset, end)
   741  	sline := spos.Line
   742  	eline := epos.Line
   743  	// first line is from spos.Column to end
   744  	tv.token(start, leng(sline)-spos.Column, tok, nil) // leng(sline)-1 - (spos.Column-1)
   745  	for i := sline + 1; i < eline; i++ {
   746  		// intermediate lines are from 1 to end
   747  		tv.token(f.LineStart(i), leng(i)-1, tok, nil) // avoid the newline
   748  	}
   749  	// last line is from 1 to epos.Column
   750  	tv.token(f.LineStart(eline), epos.Column-1, tok, nil) // columns are 1-based
   751  }
   752  
   753  // findKeyword finds a keyword rather than guessing its location
   754  func (tv *tokenVisitor) findKeyword(keyword string, start, end token.Pos) token.Pos {
   755  	offset := int(start) - tv.pgf.Tok.Base()
   756  	last := int(end) - tv.pgf.Tok.Base()
   757  	buf := tv.pgf.Src
   758  	idx := bytes.Index(buf[offset:last], []byte(keyword))
   759  	if idx != -1 {
   760  		return start + token.Pos(idx)
   761  	}
   762  	//(in unparsable programs: type _ <-<-chan int)
   763  	tv.unexpected(fmt.Sprintf("not found:%s %v", keyword, safetoken.StartPosition(tv.fset, start)))
   764  	return token.NoPos
   765  }
   766  
   767  func (tv *tokenVisitor) importSpec(d *ast.ImportSpec) {
   768  	// a local package name or the last component of the Path
   769  	if d.Name != nil {
   770  		nm := d.Name.String()
   771  		if nm != "_" && nm != "." {
   772  			tv.token(d.Name.Pos(), len(nm), semtok.TokNamespace, nil)
   773  		}
   774  		return // don't mark anything for . or _
   775  	}
   776  	importPath := metadata.UnquoteImportPath(d)
   777  	if importPath == "" {
   778  		return
   779  	}
   780  	// Import strings are implementation defined. Try to match with parse information.
   781  	depID := tv.pkg.Metadata().DepsByImpPath[importPath]
   782  	if depID == "" {
   783  		return
   784  	}
   785  	depMD := tv.metadataSource.Metadata(depID)
   786  	if depMD == nil {
   787  		// unexpected, but impact is that maybe some import is not colored
   788  		return
   789  	}
   790  	// Check whether the original literal contains the package's declared name.
   791  	j := strings.LastIndex(d.Path.Value, string(depMD.Name))
   792  	if j == -1 {
   793  		// Package name does not match import path, so there is nothing to report.
   794  		return
   795  	}
   796  	// Report virtual declaration at the position of the substring.
   797  	start := d.Path.Pos() + token.Pos(j)
   798  	tv.token(start, len(depMD.Name), semtok.TokNamespace, nil)
   799  }
   800  
   801  // log unexpected state
   802  func (tv *tokenVisitor) unexpected(msg string) {
   803  	if semDebug {
   804  		panic(msg)
   805  	}
   806  	event.Error(tv.ctx, tv.strStack(), errors.New(msg))
   807  }
   808  
   809  var godirectives = map[string]struct{}{
   810  	// https://pkg.go.dev/cmd/compile
   811  	"noescape":       {},
   812  	"uintptrescapes": {},
   813  	"noinline":       {},
   814  	"norace":         {},
   815  	"nosplit":        {},
   816  	"linkname":       {},
   817  
   818  	// https://pkg.go.dev/go/build
   819  	"build":               {},
   820  	"binary-only-package": {},
   821  	"embed":               {},
   822  }
   823  
   824  // Tokenize godirective at the start of the comment c, if any, and the surrounding comment.
   825  // If there is any failure, emits the entire comment as a TokComment token.
   826  // Directives are highlighted as-is, even if used incorrectly. Typically there are
   827  // dedicated analyzers that will warn about misuse.
   828  func (tv *tokenVisitor) godirective(c *ast.Comment) {
   829  	// First check if '//go:directive args...' is a valid directive.
   830  	directive, args, _ := strings.Cut(c.Text, " ")
   831  	kind, _ := stringsCutPrefix(directive, "//go:")
   832  	if _, ok := godirectives[kind]; !ok {
   833  		// Unknown go: directive.
   834  		tv.token(c.Pos(), len(c.Text), semtok.TokComment, nil)
   835  		return
   836  	}
   837  
   838  	// Make the 'go:directive' part stand out, the rest is comments.
   839  	tv.token(c.Pos(), len("//"), semtok.TokComment, nil)
   840  
   841  	directiveStart := c.Pos() + token.Pos(len("//"))
   842  	tv.token(directiveStart, len(directive[len("//"):]), semtok.TokNamespace, nil)
   843  
   844  	if len(args) > 0 {
   845  		tailStart := c.Pos() + token.Pos(len(directive)+len(" "))
   846  		tv.token(tailStart, len(args), semtok.TokComment, nil)
   847  	}
   848  }
   849  
   850  // Go 1.20 strings.CutPrefix.
   851  func stringsCutPrefix(s, prefix string) (after string, found bool) {
   852  	if !strings.HasPrefix(s, prefix) {
   853  		return s, false
   854  	}
   855  	return s[len(prefix):], true
   856  }