Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go

     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 stdversion reports uses of standard library symbols that are
     6  // "too new" for the Go version in force in the referring file.
     7  package stdversion
     8  
     9  import (
    10  	"go/ast"
    11  	"go/build"
    12  	"go/types"
    13  	"regexp"
    14  	"slices"
    15  
    16  	"golang.org/x/tools/go/analysis"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  	"golang.org/x/tools/go/ast/inspector"
    19  	"golang.org/x/tools/internal/typesinternal"
    20  	"golang.org/x/tools/internal/versions"
    21  )
    22  
    23  const Doc = `report uses of too-new standard library symbols
    24  
    25  The stdversion analyzer reports references to symbols in the standard
    26  library that were introduced by a Go release higher than the one in
    27  force in the referring file. (Recall that the file's Go version is
    28  defined by the 'go' directive its module's go.mod file, or by a
    29  "//go:build go1.X" build tag at the top of the file.)
    30  
    31  The analyzer does not report a diagnostic for a reference to a "too
    32  new" field or method of a type that is itself "too new", as this may
    33  have false positives, for example if fields or methods are accessed
    34  through a type alias that is guarded by a Go version constraint.
    35  `
    36  
    37  var Analyzer = &analysis.Analyzer{
    38  	Name:             "stdversion",
    39  	Doc:              Doc,
    40  	Requires:         []*analysis.Analyzer{inspect.Analyzer},
    41  	URL:              "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
    42  	RunDespiteErrors: true,
    43  	Run:              run,
    44  }
    45  
    46  func run(pass *analysis.Pass) (any, error) {
    47  	// Prior to go1.22, versions.FileVersion returns only the
    48  	// toolchain version, which is of no use to us, so
    49  	// disable this analyzer on earlier versions.
    50  	if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
    51  		return nil, nil
    52  	}
    53  
    54  	// Don't report diagnostics for modules marked before go1.21,
    55  	// since at that time the go directive wasn't clearly
    56  	// specified as a toolchain requirement.
    57  	pkgVersion := pass.Pkg.GoVersion()
    58  	if !versions.AtLeast(pkgVersion, "go1.21") {
    59  		return nil, nil
    60  	}
    61  
    62  	// disallowedSymbols returns the set of standard library symbols
    63  	// in a given package that are disallowed at the specified Go version.
    64  	type key struct {
    65  		pkg     *types.Package
    66  		version string
    67  	}
    68  	memo := make(map[key]map[types.Object]string) // records symbol's minimum Go version
    69  	disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
    70  		k := key{pkg, version}
    71  		disallowed, ok := memo[k]
    72  		if !ok {
    73  			disallowed = typesinternal.TooNewStdSymbols(pkg, version)
    74  			memo[k] = disallowed
    75  		}
    76  		return disallowed
    77  	}
    78  
    79  	// Scan the syntax looking for references to symbols
    80  	// that are disallowed by the version of the file.
    81  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    82  	nodeFilter := []ast.Node{
    83  		(*ast.File)(nil),
    84  		(*ast.Ident)(nil),
    85  	}
    86  	var fileVersion string // "" => no check
    87  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    88  		switch n := n.(type) {
    89  		case *ast.File:
    90  			if ast.IsGenerated(n) {
    91  				// Suppress diagnostics in generated files (such as cgo).
    92  				fileVersion = ""
    93  			} else {
    94  				fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
    95  				// (may be "" if unknown)
    96  			}
    97  
    98  		case *ast.Ident:
    99  			if fileVersion != "" {
   100  				if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
   101  					disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
   102  					if minVersion, ok := disallowed[origin(obj)]; ok {
   103  						noun := "module"
   104  						if fileVersion != pkgVersion {
   105  							noun = "file"
   106  						}
   107  						pass.ReportRangef(n, "%s.%s requires %v or later (%s is %s)",
   108  							obj.Pkg().Name(), obj.Name(), minVersion, noun, fileVersion)
   109  					}
   110  				}
   111  			}
   112  		}
   113  	})
   114  	return nil, nil
   115  }
   116  
   117  // Matches cgo generated comment as well as the proposed standard:
   118  //
   119  //	https://golang.org/s/generatedcode
   120  var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`)
   121  
   122  // origin returns the original uninstantiated symbol for obj.
   123  func origin(obj types.Object) types.Object {
   124  	switch obj := obj.(type) {
   125  	case *types.Var:
   126  		return obj.Origin()
   127  	case *types.Func:
   128  		return obj.Origin()
   129  	case *types.TypeName:
   130  		if named, ok := obj.Type().(*types.Named); ok { // (don't unalias)
   131  			return named.Origin().Obj()
   132  		}
   133  	}
   134  	return obj
   135  }
   136  

View as plain text