github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/source/util.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 source
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/printer"
    12  	"go/token"
    13  	"go/types"
    14  	"path/filepath"
    15  	"regexp"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  
    20  	"golang.org/x/mod/modfile"
    21  	"github.com/powerman/golang-tools/internal/lsp/protocol"
    22  	"github.com/powerman/golang-tools/internal/span"
    23  	errors "golang.org/x/xerrors"
    24  )
    25  
    26  // MappedRange provides mapped protocol.Range for a span.Range, accounting for
    27  // UTF-16 code points.
    28  type MappedRange struct {
    29  	spanRange span.Range
    30  	m         *protocol.ColumnMapper
    31  
    32  	// protocolRange is the result of converting the spanRange using the mapper.
    33  	// It is computed on-demand.
    34  	protocolRange *protocol.Range
    35  }
    36  
    37  // NewMappedRange returns a MappedRange for the given start and end token.Pos.
    38  func NewMappedRange(fset *token.FileSet, m *protocol.ColumnMapper, start, end token.Pos) MappedRange {
    39  	return MappedRange{
    40  		spanRange: span.Range{
    41  			FileSet:   fset,
    42  			Start:     start,
    43  			End:       end,
    44  			Converter: m.Converter,
    45  		},
    46  		m: m,
    47  	}
    48  }
    49  
    50  func (s MappedRange) Range() (protocol.Range, error) {
    51  	if s.protocolRange == nil {
    52  		spn, err := s.spanRange.Span()
    53  		if err != nil {
    54  			return protocol.Range{}, err
    55  		}
    56  		prng, err := s.m.Range(spn)
    57  		if err != nil {
    58  			return protocol.Range{}, err
    59  		}
    60  		s.protocolRange = &prng
    61  	}
    62  	return *s.protocolRange, nil
    63  }
    64  
    65  func (s MappedRange) Span() (span.Span, error) {
    66  	return s.spanRange.Span()
    67  }
    68  
    69  func (s MappedRange) SpanRange() span.Range {
    70  	return s.spanRange
    71  }
    72  
    73  func (s MappedRange) URI() span.URI {
    74  	return s.m.URI
    75  }
    76  
    77  // GetParsedFile is a convenience function that extracts the Package and
    78  // ParsedGoFile for a file in a Snapshot. pkgPolicy is one of NarrowestPackage/
    79  // WidestPackage.
    80  func GetParsedFile(ctx context.Context, snapshot Snapshot, fh FileHandle, pkgPolicy PackageFilter) (Package, *ParsedGoFile, error) {
    81  	pkg, err := snapshot.PackageForFile(ctx, fh.URI(), TypecheckWorkspace, pkgPolicy)
    82  	if err != nil {
    83  		return nil, nil, err
    84  	}
    85  	pgh, err := pkg.File(fh.URI())
    86  	return pkg, pgh, err
    87  }
    88  
    89  func IsGenerated(ctx context.Context, snapshot Snapshot, uri span.URI) bool {
    90  	fh, err := snapshot.GetFile(ctx, uri)
    91  	if err != nil {
    92  		return false
    93  	}
    94  	pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader)
    95  	if err != nil {
    96  		return false
    97  	}
    98  	tok := snapshot.FileSet().File(pgf.File.Pos())
    99  	if tok == nil {
   100  		return false
   101  	}
   102  	for _, commentGroup := range pgf.File.Comments {
   103  		for _, comment := range commentGroup.List {
   104  			if matched := generatedRx.MatchString(comment.Text); matched {
   105  				// Check if comment is at the beginning of the line in source.
   106  				if pos := tok.Position(comment.Slash); pos.Column == 1 {
   107  					return true
   108  				}
   109  			}
   110  		}
   111  	}
   112  	return false
   113  }
   114  
   115  func nodeToProtocolRange(snapshot Snapshot, pkg Package, n ast.Node) (protocol.Range, error) {
   116  	mrng, err := posToMappedRange(snapshot, pkg, n.Pos(), n.End())
   117  	if err != nil {
   118  		return protocol.Range{}, err
   119  	}
   120  	return mrng.Range()
   121  }
   122  
   123  func objToMappedRange(snapshot Snapshot, pkg Package, obj types.Object) (MappedRange, error) {
   124  	if pkgName, ok := obj.(*types.PkgName); ok {
   125  		// An imported Go package has a package-local, unqualified name.
   126  		// When the name matches the imported package name, there is no
   127  		// identifier in the import spec with the local package name.
   128  		//
   129  		// For example:
   130  		// 		import "go/ast" 	// name "ast" matches package name
   131  		// 		import a "go/ast"  	// name "a" does not match package name
   132  		//
   133  		// When the identifier does not appear in the source, have the range
   134  		// of the object be the import path, including quotes.
   135  		if pkgName.Imported().Name() == pkgName.Name() {
   136  			return posToMappedRange(snapshot, pkg, obj.Pos(), obj.Pos()+token.Pos(len(pkgName.Imported().Path())+2))
   137  		}
   138  	}
   139  	return nameToMappedRange(snapshot, pkg, obj.Pos(), obj.Name())
   140  }
   141  
   142  func nameToMappedRange(snapshot Snapshot, pkg Package, pos token.Pos, name string) (MappedRange, error) {
   143  	return posToMappedRange(snapshot, pkg, pos, pos+token.Pos(len(name)))
   144  }
   145  
   146  func posToMappedRange(snapshot Snapshot, pkg Package, pos, end token.Pos) (MappedRange, error) {
   147  	logicalFilename := snapshot.FileSet().File(pos).Position(pos).Filename
   148  	pgf, _, err := findFileInDeps(pkg, span.URIFromPath(logicalFilename))
   149  	if err != nil {
   150  		return MappedRange{}, err
   151  	}
   152  	if !pos.IsValid() {
   153  		return MappedRange{}, errors.Errorf("invalid position for %v", pos)
   154  	}
   155  	if !end.IsValid() {
   156  		return MappedRange{}, errors.Errorf("invalid position for %v", end)
   157  	}
   158  	return NewMappedRange(snapshot.FileSet(), pgf.Mapper, pos, end), nil
   159  }
   160  
   161  // Matches cgo generated comment as well as the proposed standard:
   162  //	https://golang.org/s/generatedcode
   163  var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
   164  
   165  // FileKindForLang returns the file kind associated with the given language ID,
   166  // or UnknownKind if the language ID is not recognized.
   167  func FileKindForLang(langID string) FileKind {
   168  	switch langID {
   169  	case "go":
   170  		return Go
   171  	case "go.mod":
   172  		return Mod
   173  	case "go.sum":
   174  		return Sum
   175  	case "tmpl", "gotmpl":
   176  		return Tmpl
   177  	case "go.work":
   178  		return Work
   179  	default:
   180  		return UnknownKind
   181  	}
   182  }
   183  
   184  func (k FileKind) String() string {
   185  	switch k {
   186  	case Go:
   187  		return "go"
   188  	case Mod:
   189  		return "go.mod"
   190  	case Sum:
   191  		return "go.sum"
   192  	case Tmpl:
   193  		return "tmpl"
   194  	case Work:
   195  		return "go.work"
   196  	default:
   197  		return fmt.Sprintf("unk%d", k)
   198  	}
   199  }
   200  
   201  // nodeAtPos returns the index and the node whose position is contained inside
   202  // the node list.
   203  func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
   204  	if nodes == nil {
   205  		return nil, -1
   206  	}
   207  	for i, node := range nodes {
   208  		if node.Pos() <= pos && pos <= node.End() {
   209  			return node, i
   210  		}
   211  	}
   212  	return nil, -1
   213  }
   214  
   215  // IsInterface returns if a types.Type is an interface
   216  func IsInterface(T types.Type) bool {
   217  	return T != nil && types.IsInterface(T)
   218  }
   219  
   220  // FormatNode returns the "pretty-print" output for an ast node.
   221  func FormatNode(fset *token.FileSet, n ast.Node) string {
   222  	var buf strings.Builder
   223  	if err := printer.Fprint(&buf, fset, n); err != nil {
   224  		return ""
   225  	}
   226  	return buf.String()
   227  }
   228  
   229  // Deref returns a pointer's element type, traversing as many levels as needed.
   230  // Otherwise it returns typ.
   231  //
   232  // It can return a pointer type for cyclic types (see golang/go#45510).
   233  func Deref(typ types.Type) types.Type {
   234  	var seen map[types.Type]struct{}
   235  	for {
   236  		p, ok := typ.Underlying().(*types.Pointer)
   237  		if !ok {
   238  			return typ
   239  		}
   240  		if _, ok := seen[p.Elem()]; ok {
   241  			return typ
   242  		}
   243  
   244  		typ = p.Elem()
   245  
   246  		if seen == nil {
   247  			seen = make(map[types.Type]struct{})
   248  		}
   249  		seen[typ] = struct{}{}
   250  	}
   251  }
   252  
   253  func SortDiagnostics(d []*Diagnostic) {
   254  	sort.Slice(d, func(i int, j int) bool {
   255  		return CompareDiagnostic(d[i], d[j]) < 0
   256  	})
   257  }
   258  
   259  func CompareDiagnostic(a, b *Diagnostic) int {
   260  	if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
   261  		return r
   262  	}
   263  	if a.Source < b.Source {
   264  		return -1
   265  	}
   266  	if a.Message < b.Message {
   267  		return -1
   268  	}
   269  	if a.Message == b.Message {
   270  		return 0
   271  	}
   272  	return 1
   273  }
   274  
   275  // FindPackageFromPos finds the first package containing pos in its
   276  // type-checked AST.
   277  func FindPackageFromPos(ctx context.Context, snapshot Snapshot, pos token.Pos) (Package, error) {
   278  	tok := snapshot.FileSet().File(pos)
   279  	if tok == nil {
   280  		return nil, errors.Errorf("no file for pos %v", pos)
   281  	}
   282  	uri := span.URIFromPath(tok.Name())
   283  	pkgs, err := snapshot.PackagesForFile(ctx, uri, TypecheckAll, true)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	// Only return the package if it actually type-checked the given position.
   288  	for _, pkg := range pkgs {
   289  		parsed, err := pkg.File(uri)
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  		if parsed == nil {
   294  			continue
   295  		}
   296  		if parsed.Tok.Base() != tok.Base() {
   297  			continue
   298  		}
   299  		return pkg, nil
   300  	}
   301  	return nil, errors.Errorf("no package for given file position")
   302  }
   303  
   304  // findFileInDeps finds uri in pkg or its dependencies.
   305  func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
   306  	queue := []Package{pkg}
   307  	seen := make(map[string]bool)
   308  
   309  	for len(queue) > 0 {
   310  		pkg := queue[0]
   311  		queue = queue[1:]
   312  		seen[pkg.ID()] = true
   313  
   314  		if pgf, err := pkg.File(uri); err == nil {
   315  			return pgf, pkg, nil
   316  		}
   317  		for _, dep := range pkg.Imports() {
   318  			if !seen[dep.ID()] {
   319  				queue = append(queue, dep)
   320  			}
   321  		}
   322  	}
   323  	return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
   324  }
   325  
   326  // ImportPath returns the unquoted import path of s,
   327  // or "" if the path is not properly quoted.
   328  func ImportPath(s *ast.ImportSpec) string {
   329  	t, err := strconv.Unquote(s.Path.Value)
   330  	if err != nil {
   331  		return ""
   332  	}
   333  	return t
   334  }
   335  
   336  // NodeContains returns true if a node encloses a given position pos.
   337  func NodeContains(n ast.Node, pos token.Pos) bool {
   338  	return n != nil && n.Pos() <= pos && pos <= n.End()
   339  }
   340  
   341  // CollectScopes returns all scopes in an ast path, ordered as innermost scope
   342  // first.
   343  func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope {
   344  	// scopes[i], where i<len(path), is the possibly nil Scope of path[i].
   345  	var scopes []*types.Scope
   346  	for _, n := range path {
   347  		// Include *FuncType scope if pos is inside the function body.
   348  		switch node := n.(type) {
   349  		case *ast.FuncDecl:
   350  			if node.Body != nil && NodeContains(node.Body, pos) {
   351  				n = node.Type
   352  			}
   353  		case *ast.FuncLit:
   354  			if node.Body != nil && NodeContains(node.Body, pos) {
   355  				n = node.Type
   356  			}
   357  		}
   358  		scopes = append(scopes, info.Scopes[n])
   359  	}
   360  	return scopes
   361  }
   362  
   363  // Qualifier returns a function that appropriately formats a types.PkgName
   364  // appearing in a *ast.File.
   365  func Qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
   366  	// Construct mapping of import paths to their defined or implicit names.
   367  	imports := make(map[*types.Package]string)
   368  	for _, imp := range f.Imports {
   369  		var obj types.Object
   370  		if imp.Name != nil {
   371  			obj = info.Defs[imp.Name]
   372  		} else {
   373  			obj = info.Implicits[imp]
   374  		}
   375  		if pkgname, ok := obj.(*types.PkgName); ok {
   376  			imports[pkgname.Imported()] = pkgname.Name()
   377  		}
   378  	}
   379  	// Define qualifier to replace full package paths with names of the imports.
   380  	return func(p *types.Package) string {
   381  		if p == pkg {
   382  			return ""
   383  		}
   384  		if name, ok := imports[p]; ok {
   385  			if name == "." {
   386  				return ""
   387  			}
   388  			return name
   389  		}
   390  		return p.Name()
   391  	}
   392  }
   393  
   394  // isDirective reports whether c is a comment directive.
   395  //
   396  // Copied and adapted from go/src/go/ast/ast.go.
   397  func isDirective(c string) bool {
   398  	if len(c) < 3 {
   399  		return false
   400  	}
   401  	if c[1] != '/' {
   402  		return false
   403  	}
   404  	//-style comment (no newline at the end)
   405  	c = c[2:]
   406  	if len(c) == 0 {
   407  		// empty line
   408  		return false
   409  	}
   410  	// "//line " is a line directive.
   411  	// (The // has been removed.)
   412  	if strings.HasPrefix(c, "line ") {
   413  		return true
   414  	}
   415  
   416  	// "//[a-z0-9]+:[a-z0-9]"
   417  	// (The // has been removed.)
   418  	colon := strings.Index(c, ":")
   419  	if colon <= 0 || colon+1 >= len(c) {
   420  		return false
   421  	}
   422  	for i := 0; i <= colon+1; i++ {
   423  		if i == colon {
   424  			continue
   425  		}
   426  		b := c[i]
   427  		if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
   428  			return false
   429  		}
   430  	}
   431  	return true
   432  }
   433  
   434  // honorSymlinks toggles whether or not we consider symlinks when comparing
   435  // file or directory URIs.
   436  const honorSymlinks = false
   437  
   438  func CompareURI(left, right span.URI) int {
   439  	if honorSymlinks {
   440  		return span.CompareURI(left, right)
   441  	}
   442  	if left == right {
   443  		return 0
   444  	}
   445  	if left < right {
   446  		return -1
   447  	}
   448  	return 1
   449  }
   450  
   451  // InDir checks whether path is in the file tree rooted at dir.
   452  // InDir makes some effort to succeed even in the presence of symbolic links.
   453  //
   454  // Copied and slightly adjusted from go/src/cmd/go/internal/search/search.go.
   455  func InDir(dir, path string) bool {
   456  	if inDirLex(dir, path) {
   457  		return true
   458  	}
   459  	if !honorSymlinks {
   460  		return false
   461  	}
   462  	xpath, err := filepath.EvalSymlinks(path)
   463  	if err != nil || xpath == path {
   464  		xpath = ""
   465  	} else {
   466  		if inDirLex(dir, xpath) {
   467  			return true
   468  		}
   469  	}
   470  
   471  	xdir, err := filepath.EvalSymlinks(dir)
   472  	if err == nil && xdir != dir {
   473  		if inDirLex(xdir, path) {
   474  			return true
   475  		}
   476  		if xpath != "" {
   477  			if inDirLex(xdir, xpath) {
   478  				return true
   479  			}
   480  		}
   481  	}
   482  	return false
   483  }
   484  
   485  // inDirLex is like inDir but only checks the lexical form of the file names.
   486  // It does not consider symbolic links.
   487  //
   488  // Copied from go/src/cmd/go/internal/search/search.go.
   489  func inDirLex(dir, path string) bool {
   490  	pv := strings.ToUpper(filepath.VolumeName(path))
   491  	dv := strings.ToUpper(filepath.VolumeName(dir))
   492  	path = path[len(pv):]
   493  	dir = dir[len(dv):]
   494  	switch {
   495  	default:
   496  		return false
   497  	case pv != dv:
   498  		return false
   499  	case len(path) == len(dir):
   500  		if path == dir {
   501  			return true
   502  		}
   503  		return false
   504  	case dir == "":
   505  		return path != ""
   506  	case len(path) > len(dir):
   507  		if dir[len(dir)-1] == filepath.Separator {
   508  			if path[:len(dir)] == dir {
   509  				return path[len(dir):] != ""
   510  			}
   511  			return false
   512  		}
   513  		if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
   514  			if len(path) == len(dir)+1 {
   515  				return true
   516  			}
   517  			return path[len(dir)+1:] != ""
   518  		}
   519  		return false
   520  	}
   521  }
   522  
   523  // IsValidImport returns whether importPkgPath is importable
   524  // by pkgPath
   525  func IsValidImport(pkgPath, importPkgPath string) bool {
   526  	i := strings.LastIndex(string(importPkgPath), "/internal/")
   527  	if i == -1 {
   528  		return true
   529  	}
   530  	if IsCommandLineArguments(string(pkgPath)) {
   531  		return true
   532  	}
   533  	return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
   534  }
   535  
   536  // IsCommandLineArguments reports whether a given value denotes
   537  // "command-line-arguments" package, which is a package with an unknown ID
   538  // created by the go command. It can have a test variant, which is why callers
   539  // should not check that a value equals "command-line-arguments" directly.
   540  func IsCommandLineArguments(s string) bool {
   541  	return strings.Contains(s, "command-line-arguments")
   542  }
   543  
   544  // Offset returns tok.Offset(pos), but first checks that the pos is in range
   545  // for the given file.
   546  func Offset(tok *token.File, pos token.Pos) (int, error) {
   547  	if !InRange(tok, pos) {
   548  		return -1, fmt.Errorf("pos %v is not in range for file [%v:%v)", pos, tok.Base(), tok.Base()+tok.Size())
   549  	}
   550  	return tok.Offset(pos), nil
   551  }
   552  
   553  // Pos returns tok.Pos(offset), but first checks that the offset is valid for
   554  // the given file.
   555  func Pos(tok *token.File, offset int) (token.Pos, error) {
   556  	if offset < 0 || offset > tok.Size() {
   557  		return token.NoPos, fmt.Errorf("offset %v is not in range for file of size %v", offset, tok.Size())
   558  	}
   559  	return tok.Pos(offset), nil
   560  }
   561  
   562  // InRange reports whether the given position is in the given token.File.
   563  func InRange(tok *token.File, pos token.Pos) bool {
   564  	size := tok.Pos(tok.Size())
   565  	return int(pos) >= tok.Base() && pos <= size
   566  }
   567  
   568  // LineToRange creates a Range spanning start and end.
   569  func LineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
   570  	return ByteOffsetsToRange(m, uri, start.Byte, end.Byte)
   571  }
   572  
   573  // ByteOffsetsToRange creates a range spanning start and end.
   574  func ByteOffsetsToRange(m *protocol.ColumnMapper, uri span.URI, start, end int) (protocol.Range, error) {
   575  	line, col, err := m.Converter.ToPosition(start)
   576  	if err != nil {
   577  		return protocol.Range{}, err
   578  	}
   579  	s := span.NewPoint(line, col, start)
   580  	line, col, err = m.Converter.ToPosition(end)
   581  	if err != nil {
   582  		return protocol.Range{}, err
   583  	}
   584  	e := span.NewPoint(line, col, end)
   585  	return m.Range(span.New(uri, s, e))
   586  }