golang.org/x/tools/gopls@v0.15.3/internal/golang/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 golang
     6  
     7  import (
     8  	"context"
     9  	"go/ast"
    10  	"go/printer"
    11  	"go/token"
    12  	"go/types"
    13  	"regexp"
    14  	"strings"
    15  
    16  	"golang.org/x/tools/gopls/internal/cache"
    17  	"golang.org/x/tools/gopls/internal/cache/metadata"
    18  	"golang.org/x/tools/gopls/internal/protocol"
    19  	"golang.org/x/tools/gopls/internal/util/astutil"
    20  	"golang.org/x/tools/gopls/internal/util/bug"
    21  	"golang.org/x/tools/gopls/internal/util/safetoken"
    22  	"golang.org/x/tools/internal/tokeninternal"
    23  )
    24  
    25  // IsGenerated gets and reads the file denoted by uri and reports
    26  // whether it contains a "generated file" comment as described at
    27  // https://golang.org/s/generatedcode.
    28  //
    29  // TODO(adonovan): opt: this function does too much.
    30  // Move snapshot.ReadFile into the caller (most of which have already done it).
    31  func IsGenerated(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) bool {
    32  	fh, err := snapshot.ReadFile(ctx, uri)
    33  	if err != nil {
    34  		return false
    35  	}
    36  	pgf, err := snapshot.ParseGo(ctx, fh, ParseHeader)
    37  	if err != nil {
    38  		return false
    39  	}
    40  	for _, commentGroup := range pgf.File.Comments {
    41  		for _, comment := range commentGroup.List {
    42  			if matched := generatedRx.MatchString(comment.Text); matched {
    43  				// Check if comment is at the beginning of the line in source.
    44  				if safetoken.Position(pgf.Tok, comment.Slash).Column == 1 {
    45  					return true
    46  				}
    47  			}
    48  		}
    49  	}
    50  	return false
    51  }
    52  
    53  // adjustedObjEnd returns the end position of obj, possibly modified for
    54  // package names.
    55  //
    56  // TODO(rfindley): eliminate this function, by inlining it at callsites where
    57  // it makes sense.
    58  func adjustedObjEnd(obj types.Object) token.Pos {
    59  	nameLen := len(obj.Name())
    60  	if pkgName, ok := obj.(*types.PkgName); ok {
    61  		// An imported Go package has a package-local, unqualified name.
    62  		// When the name matches the imported package name, there is no
    63  		// identifier in the import spec with the local package name.
    64  		//
    65  		// For example:
    66  		// 		import "go/ast" 	// name "ast" matches package name
    67  		// 		import a "go/ast"  	// name "a" does not match package name
    68  		//
    69  		// When the identifier does not appear in the source, have the range
    70  		// of the object be the import path, including quotes.
    71  		if pkgName.Imported().Name() == pkgName.Name() {
    72  			nameLen = len(pkgName.Imported().Path()) + len(`""`)
    73  		}
    74  	}
    75  	return obj.Pos() + token.Pos(nameLen)
    76  }
    77  
    78  // Matches cgo generated comment as well as the proposed standard:
    79  //
    80  //	https://golang.org/s/generatedcode
    81  var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
    82  
    83  // nodeAtPos returns the index and the node whose position is contained inside
    84  // the node list.
    85  func nodeAtPos(nodes []ast.Node, pos token.Pos) (ast.Node, int) {
    86  	if nodes == nil {
    87  		return nil, -1
    88  	}
    89  	for i, node := range nodes {
    90  		if node.Pos() <= pos && pos <= node.End() {
    91  			return node, i
    92  		}
    93  	}
    94  	return nil, -1
    95  }
    96  
    97  // FormatNode returns the "pretty-print" output for an ast node.
    98  func FormatNode(fset *token.FileSet, n ast.Node) string {
    99  	var buf strings.Builder
   100  	if err := printer.Fprint(&buf, fset, n); err != nil {
   101  		// TODO(rfindley): we should use bug.Reportf here.
   102  		// We encounter this during completion.resolveInvalid.
   103  		return ""
   104  	}
   105  	return buf.String()
   106  }
   107  
   108  // FormatNodeFile is like FormatNode, but requires only the token.File for the
   109  // syntax containing the given ast node.
   110  func FormatNodeFile(file *token.File, n ast.Node) string {
   111  	fset := tokeninternal.FileSetFor(file)
   112  	return FormatNode(fset, n)
   113  }
   114  
   115  // Deref returns a pointer's element type, traversing as many levels as needed.
   116  // Otherwise it returns typ.
   117  //
   118  // It can return a pointer type for cyclic types (see golang/go#45510).
   119  func Deref(typ types.Type) types.Type {
   120  	var seen map[types.Type]struct{}
   121  	for {
   122  		p, ok := typ.Underlying().(*types.Pointer)
   123  		if !ok {
   124  			return typ
   125  		}
   126  		if _, ok := seen[p.Elem()]; ok {
   127  			return typ
   128  		}
   129  
   130  		typ = p.Elem()
   131  
   132  		if seen == nil {
   133  			seen = make(map[types.Type]struct{})
   134  		}
   135  		seen[typ] = struct{}{}
   136  	}
   137  }
   138  
   139  // findFileInDeps finds package metadata containing URI in the transitive
   140  // dependencies of m. When using the Go command, the answer is unique.
   141  func findFileInDeps(s metadata.Source, mp *metadata.Package, uri protocol.DocumentURI) *metadata.Package {
   142  	seen := make(map[PackageID]bool)
   143  	var search func(*metadata.Package) *metadata.Package
   144  	search = func(mp *metadata.Package) *metadata.Package {
   145  		if seen[mp.ID] {
   146  			return nil
   147  		}
   148  		seen[mp.ID] = true
   149  		for _, cgf := range mp.CompiledGoFiles {
   150  			if cgf == uri {
   151  				return mp
   152  			}
   153  		}
   154  		for _, dep := range mp.DepsByPkgPath {
   155  			mp := s.Metadata(dep)
   156  			if mp == nil {
   157  				bug.Reportf("nil metadata for %q", dep)
   158  				continue
   159  			}
   160  			if found := search(mp); found != nil {
   161  				return found
   162  			}
   163  		}
   164  		return nil
   165  	}
   166  	return search(mp)
   167  }
   168  
   169  // CollectScopes returns all scopes in an ast path, ordered as innermost scope
   170  // first.
   171  func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope {
   172  	// scopes[i], where i<len(path), is the possibly nil Scope of path[i].
   173  	var scopes []*types.Scope
   174  	for _, n := range path {
   175  		// Include *FuncType scope if pos is inside the function body.
   176  		switch node := n.(type) {
   177  		case *ast.FuncDecl:
   178  			if node.Body != nil && astutil.NodeContains(node.Body, pos) {
   179  				n = node.Type
   180  			}
   181  		case *ast.FuncLit:
   182  			if node.Body != nil && astutil.NodeContains(node.Body, pos) {
   183  				n = node.Type
   184  			}
   185  		}
   186  		scopes = append(scopes, info.Scopes[n])
   187  	}
   188  	return scopes
   189  }
   190  
   191  // requalifier returns a function that re-qualifies identifiers and qualified
   192  // identifiers contained in targetFile using the given metadata qualifier.
   193  func requalifier(s metadata.Source, targetFile *ast.File, targetMeta *metadata.Package, mq MetadataQualifier) func(string) string {
   194  	qm := map[string]string{
   195  		"": mq(targetMeta.Name, "", targetMeta.PkgPath),
   196  	}
   197  
   198  	// Construct mapping of import paths to their defined or implicit names.
   199  	for _, imp := range targetFile.Imports {
   200  		name, pkgName, impPath, pkgPath := importInfo(s, imp, targetMeta)
   201  
   202  		// Re-map the target name for the source file.
   203  		qm[name] = mq(pkgName, impPath, pkgPath)
   204  	}
   205  
   206  	return func(name string) string {
   207  		if newName, ok := qm[name]; ok {
   208  			return newName
   209  		}
   210  		return name
   211  	}
   212  }
   213  
   214  // A MetadataQualifier is a function that qualifies an identifier declared in a
   215  // package with the given package name, import path, and package path.
   216  //
   217  // In scenarios where metadata is missing the provided PackageName and
   218  // PackagePath may be empty, but ImportPath must always be non-empty.
   219  type MetadataQualifier func(PackageName, ImportPath, PackagePath) string
   220  
   221  // MetadataQualifierForFile returns a metadata qualifier that chooses the best
   222  // qualification of an imported package relative to the file f in package with
   223  // metadata m.
   224  func MetadataQualifierForFile(s metadata.Source, f *ast.File, mp *metadata.Package) MetadataQualifier {
   225  	// Record local names for import paths.
   226  	localNames := make(map[ImportPath]string) // local names for imports in f
   227  	for _, imp := range f.Imports {
   228  		name, _, impPath, _ := importInfo(s, imp, mp)
   229  		localNames[impPath] = name
   230  	}
   231  
   232  	// Record a package path -> import path mapping.
   233  	inverseDeps := make(map[PackageID]PackagePath)
   234  	for path, id := range mp.DepsByPkgPath {
   235  		inverseDeps[id] = path
   236  	}
   237  	importsByPkgPath := make(map[PackagePath]ImportPath) // best import paths by pkgPath
   238  	for impPath, id := range mp.DepsByImpPath {
   239  		if id == "" {
   240  			continue
   241  		}
   242  		pkgPath := inverseDeps[id]
   243  		_, hasPath := importsByPkgPath[pkgPath]
   244  		_, hasImp := localNames[impPath]
   245  		// In rare cases, there may be multiple import paths with the same package
   246  		// path. In such scenarios, prefer an import path that already exists in
   247  		// the file.
   248  		if !hasPath || hasImp {
   249  			importsByPkgPath[pkgPath] = impPath
   250  		}
   251  	}
   252  
   253  	return func(pkgName PackageName, impPath ImportPath, pkgPath PackagePath) string {
   254  		// If supplied, translate the package path to an import path in the source
   255  		// package.
   256  		if pkgPath != "" {
   257  			if srcImp := importsByPkgPath[pkgPath]; srcImp != "" {
   258  				impPath = srcImp
   259  			}
   260  			if pkgPath == mp.PkgPath {
   261  				return ""
   262  			}
   263  		}
   264  		if localName, ok := localNames[impPath]; ok && impPath != "" {
   265  			return localName
   266  		}
   267  		if pkgName != "" {
   268  			return string(pkgName)
   269  		}
   270  		idx := strings.LastIndexByte(string(impPath), '/')
   271  		return string(impPath[idx+1:])
   272  	}
   273  }
   274  
   275  // importInfo collects information about the import specified by imp,
   276  // extracting its file-local name, package name, import path, and package path.
   277  //
   278  // If metadata is missing for the import, the resulting package name and
   279  // package path may be empty, and the file local name may be guessed based on
   280  // the import path.
   281  //
   282  // Note: previous versions of this helper used a PackageID->PackagePath map
   283  // extracted from m, for extracting package path even in the case where
   284  // metadata for a dep was missing. This should not be necessary, as we should
   285  // always have metadata for IDs contained in DepsByPkgPath.
   286  func importInfo(s metadata.Source, imp *ast.ImportSpec, mp *metadata.Package) (string, PackageName, ImportPath, PackagePath) {
   287  	var (
   288  		name    string // local name
   289  		pkgName PackageName
   290  		impPath = metadata.UnquoteImportPath(imp)
   291  		pkgPath PackagePath
   292  	)
   293  
   294  	// If the import has a local name, use it.
   295  	if imp.Name != nil {
   296  		name = imp.Name.Name
   297  	}
   298  
   299  	// Try to find metadata for the import. If successful and there is no local
   300  	// name, the package name is the local name.
   301  	if depID := mp.DepsByImpPath[impPath]; depID != "" {
   302  		if depMP := s.Metadata(depID); depMP != nil {
   303  			if name == "" {
   304  				name = string(depMP.Name)
   305  			}
   306  			pkgName = depMP.Name
   307  			pkgPath = depMP.PkgPath
   308  		}
   309  	}
   310  
   311  	// If the local name is still unknown, guess it based on the import path.
   312  	if name == "" {
   313  		idx := strings.LastIndexByte(string(impPath), '/')
   314  		name = string(impPath[idx+1:])
   315  	}
   316  	return name, pkgName, impPath, pkgPath
   317  }
   318  
   319  // isDirective reports whether c is a comment directive.
   320  //
   321  // Copied and adapted from go/src/go/ast/ast.go.
   322  func isDirective(c string) bool {
   323  	if len(c) < 3 {
   324  		return false
   325  	}
   326  	if c[1] != '/' {
   327  		return false
   328  	}
   329  	//-style comment (no newline at the end)
   330  	c = c[2:]
   331  	if len(c) == 0 {
   332  		// empty line
   333  		return false
   334  	}
   335  	// "//line " is a line directive.
   336  	// (The // has been removed.)
   337  	if strings.HasPrefix(c, "line ") {
   338  		return true
   339  	}
   340  
   341  	// "//[a-z0-9]+:[a-z0-9]"
   342  	// (The // has been removed.)
   343  	colon := strings.Index(c, ":")
   344  	if colon <= 0 || colon+1 >= len(c) {
   345  		return false
   346  	}
   347  	for i := 0; i <= colon+1; i++ {
   348  		if i == colon {
   349  			continue
   350  		}
   351  		b := c[i]
   352  		if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
   353  			return false
   354  		}
   355  	}
   356  	return true
   357  }
   358  
   359  // embeddedIdent returns the type name identifier for an embedding x, if x in a
   360  // valid embedding. Otherwise, it returns nil.
   361  //
   362  // Spec: An embedded field must be specified as a type name T or as a pointer
   363  // to a non-interface type name *T
   364  func embeddedIdent(x ast.Expr) *ast.Ident {
   365  	if star, ok := x.(*ast.StarExpr); ok {
   366  		x = star.X
   367  	}
   368  	switch ix := x.(type) { // check for instantiated receivers
   369  	case *ast.IndexExpr:
   370  		x = ix.X
   371  	case *ast.IndexListExpr:
   372  		x = ix.X
   373  	}
   374  	switch x := x.(type) {
   375  	case *ast.Ident:
   376  		return x
   377  	case *ast.SelectorExpr:
   378  		if _, ok := x.X.(*ast.Ident); ok {
   379  			return x.Sel
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  // An importFunc is an implementation of the single-method
   386  // types.Importer interface based on a function value.
   387  type ImporterFunc func(path string) (*types.Package, error)
   388  
   389  func (f ImporterFunc) Import(path string) (*types.Package, error) { return f(path) }