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